Make PhotoPanel bound
This commit is contained in:
@@ -137,7 +137,6 @@ class API extends EventEmitter {
|
|||||||
|
|
||||||
this._apiURL = url
|
this._apiURL = url
|
||||||
this._baseURL = parts[0] + "//" + parts[1]
|
this._baseURL = parts[0] + "//" + parts[1]
|
||||||
this._secure = parts[0] === "https:"
|
|
||||||
|
|
||||||
if (parts.length === 3) {
|
if (parts.length === 3) {
|
||||||
this._apiPath = "/" + parts[2]
|
this._apiPath = "/" + parts[2]
|
||||||
@@ -155,8 +154,8 @@ class API extends EventEmitter {
|
|||||||
return this._apiPath
|
return this._apiPath
|
||||||
}
|
}
|
||||||
|
|
||||||
get secure() {
|
get apiURL() {
|
||||||
return this._secure
|
return this._apiURL
|
||||||
}
|
}
|
||||||
|
|
||||||
get backend() {
|
get backend() {
|
||||||
@@ -178,21 +177,13 @@ class API extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
makeImageUrl(id, size) {
|
makeImageUrl(id) {
|
||||||
if (id) {
|
return this._apiURL + "/assets/" + id + ".jpg?access_token=" + this.token
|
||||||
return this.apiPath + "/assets/" + id + "?access_token=" + this.token
|
|
||||||
} else if (size && size.width && size.height) {
|
|
||||||
return `${this.apiPath}/placeholders/${size.width}x${
|
|
||||||
size.height
|
|
||||||
}?access_token=${this.token}`
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
makeAssetUrl(id) {
|
makeAssetUrl(id) {
|
||||||
return id
|
return id
|
||||||
? this.apiPath + "/assets/" + id + "?access_token=" + this.token
|
? this._apiURL + "/assets/" + id + "?access_token=" + this.token
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,10 +209,17 @@ 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.binary) {
|
if (requestOptions.raw) {
|
||||||
headers.set("Content-Type", "application/octet-stream")
|
const isBase64 = requestOptions.raw.base64
|
||||||
headers.set("Content-Length", requestOptions.binary.length)
|
headers.set(
|
||||||
headers.set("Range", "byte " + requestOptions.binary.offset)
|
"Content-Type",
|
||||||
|
isBase64 ? "application/base64" : "application/octet-stream"
|
||||||
|
)
|
||||||
|
headers.set("Content-Length", requestOptions.raw.length)
|
||||||
|
headers.set(
|
||||||
|
"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")
|
||||||
@@ -238,7 +236,7 @@ class API extends EventEmitter {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
Promise.resolve(res),
|
Promise.resolve(res),
|
||||||
requestOptions.binary && method === "GET" ? res.blob() : res.json(),
|
requestOptions.raw && method === "GET" ? res.blob() : res.json(),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
.then((arr) => {
|
.then((arr) => {
|
||||||
@@ -427,33 +425,29 @@ class API extends EventEmitter {
|
|||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
upload(file, progressCallback) {
|
upload(data, progressCallback) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const chunkSize = 32 * 1024
|
const chunkSize = 32 * 1024
|
||||||
let reader = new FileReader()
|
const uploadSize = data.length
|
||||||
const fileSize = file.size
|
const numberOfChunks = Math.ceil(uploadSize / chunkSize)
|
||||||
const numberOfChunks = Math.ceil(fileSize / chunkSize)
|
|
||||||
let chunk = 0
|
let chunk = 0
|
||||||
let uploadId = null
|
let uploadId = null
|
||||||
|
|
||||||
reader.onload = (e) => {
|
const uploadNextChunk = () => {
|
||||||
const buffer = e.target.result
|
const start = chunk * chunkSize
|
||||||
const bytesRead = buffer.byteLength
|
const end = Math.min(uploadSize, start + chunkSize)
|
||||||
|
|
||||||
this.post("/assets/upload/" + uploadId, buffer, {
|
this.post("/assets/upload/" + uploadId, data.slice(start, end), {
|
||||||
binary: { offset: chunk * chunkSize, length: bytesRead },
|
raw: { base64: true, length: chunkSize, offset: start },
|
||||||
})
|
})
|
||||||
.then((uploadData) => {
|
.then((uploadData) => {
|
||||||
chunk++
|
chunk++
|
||||||
if (!progressCallback(uploadData)) {
|
if (progressCallback && !progressCallback(uploadData)) {
|
||||||
return Promise.reject(new Error("Upload was canceled"))
|
reject(new Error("Upload was canceled"))
|
||||||
}
|
} else if (chunk >= numberOfChunks) {
|
||||||
if (chunk < numberOfChunks) {
|
|
||||||
let start = chunk * chunkSize
|
|
||||||
let end = Math.min(fileSize, start + chunkSize)
|
|
||||||
reader.readAsArrayBuffer(file.slice(start, end))
|
|
||||||
} else {
|
|
||||||
resolve(uploadData)
|
resolve(uploadData)
|
||||||
|
} else {
|
||||||
|
uploadNextChunk()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -462,14 +456,14 @@ class API extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.post("/assets/upload", {
|
this.post("/assets/upload", {
|
||||||
fileName: file.name,
|
uploadSize,
|
||||||
fileSize,
|
contentType: "image/jpeg",
|
||||||
contentType: file.type,
|
chunkContentType: "application/base64",
|
||||||
numberOfChunks,
|
numberOfChunks,
|
||||||
})
|
})
|
||||||
.then((uploadData) => {
|
.then((uploadData) => {
|
||||||
uploadId = uploadData.uploadId
|
uploadId = uploadData.uploadId
|
||||||
reader.readAsArrayBuffer(file.slice(0, chunkSize))
|
uploadNextChunk()
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
reject(err)
|
reject(err)
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import {
|
|||||||
BoundButton,
|
BoundButton,
|
||||||
BoundOptionStrip,
|
BoundOptionStrip,
|
||||||
BoundHeader,
|
BoundHeader,
|
||||||
PhotoPanel,
|
BoundPhotoPanel,
|
||||||
} from "../ui"
|
} from "../ui"
|
||||||
import { MessageModal } from "../Modal"
|
import { MessageModal, WaitModal } from "../Modal"
|
||||||
import autobind from "autobind-decorator"
|
import autobind from "autobind-decorator"
|
||||||
import KeyboardSpacer from "react-native-keyboard-spacer"
|
import KeyboardSpacer from "react-native-keyboard-spacer"
|
||||||
import { isIphoneX } from "react-native-iphone-x-helper"
|
import { isIphoneX } from "react-native-iphone-x-helper"
|
||||||
@@ -70,6 +70,9 @@ export class Activity extends React.Component {
|
|||||||
notes: {
|
notes: {
|
||||||
isValid: (r, v) => v !== "",
|
isValid: (r, v) => v !== "",
|
||||||
},
|
},
|
||||||
|
photos: {
|
||||||
|
isValid: (r, v) => v && v.length > 0,
|
||||||
|
},
|
||||||
status: {
|
status: {
|
||||||
isValid: (r, v) => v !== "",
|
isValid: (r, v) => v !== "",
|
||||||
alwaysGet: true,
|
alwaysGet: true,
|
||||||
@@ -84,6 +87,7 @@ export class Activity extends React.Component {
|
|||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
binder: new FormBinder({}, Activity.bindings),
|
binder: new FormBinder({}, Activity.bindings),
|
||||||
|
waitModal: null,
|
||||||
messageModal: null,
|
messageModal: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,8 +184,18 @@ export class Activity extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleUploadStarted() {
|
||||||
|
this.setState({ waitModal: { message: "Uploading Photo..." } })
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleUploadEnded() {
|
||||||
|
this.setState({ waitModal: null })
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { binder, messageModal, region } = this.state
|
const { binder, messageModal, waitModal, region } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ width: "100%", height: "100%" }}>
|
<View style={{ width: "100%", height: "100%" }}>
|
||||||
@@ -258,10 +272,19 @@ export class Activity extends React.Component {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.panel}>
|
<View style={styles.panel}>
|
||||||
<PhotoPanel />
|
<BoundPhotoPanel
|
||||||
|
name="photos"
|
||||||
|
binder={binder}
|
||||||
|
onUploadStarted={this.handleUploadStarted}
|
||||||
|
onUploadEnded={this.handleUploadEnded}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
{isIphoneX ? <View style={{ height: 30, width: "100%" }} /> : null}
|
{isIphoneX ? <View style={{ height: 30, width: "100%" }} /> : null}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
<WaitModal
|
||||||
|
open={!!waitModal}
|
||||||
|
message={waitModal ? waitModal.message : ""}
|
||||||
|
/>
|
||||||
<MessageModal
|
<MessageModal
|
||||||
open={!!messageModal}
|
open={!!messageModal}
|
||||||
icon={messageModal ? messageModal.icon : ""}
|
icon={messageModal ? messageModal.icon : ""}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ import { Route, Redirect } from "react-router-native"
|
|||||||
|
|
||||||
export const DefaultRoute = () => {
|
export const DefaultRoute = () => {
|
||||||
// NOTE: When working on the app, change this to the page you are working on
|
// NOTE: When working on the app, change this to the page you are working on
|
||||||
return <Route render={() => <Redirect to={"/workItem"} />} />
|
return <Route render={() => <Redirect to={"/home"} />} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ export class Home extends React.Component {
|
|||||||
workItemDistance: -1,
|
workItemDistance: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.watchId = null
|
||||||
|
|
||||||
ensurePermissions(
|
ensurePermissions(
|
||||||
[
|
[
|
||||||
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
||||||
@@ -173,8 +175,20 @@ export class Home extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleItemSelect(item) {
|
handleItemSelect(activity) {
|
||||||
this.props.history.push(`/activity?id=${item._id}`)
|
this.props.history.push(`/activity?id=${activity._id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleSectionSelect(workItem) {
|
||||||
|
const { latitude, longitude } = workItem.coordinate
|
||||||
|
const region = {
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
latitudeDelta: 0.01,
|
||||||
|
longitudeDelta: 0.01,
|
||||||
|
}
|
||||||
|
this.setState({ region })
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -276,6 +290,7 @@ export class Home extends React.Component {
|
|||||||
showsTraffic={false}
|
showsTraffic={false}
|
||||||
showsIndoors={false}
|
showsIndoors={false}
|
||||||
zoomControlEnabled={false}
|
zoomControlEnabled={false}
|
||||||
|
showsMyLocationButton={false}
|
||||||
region={region}>
|
region={region}>
|
||||||
{sections.map((workItem, index) => (
|
{sections.map((workItem, index) => (
|
||||||
<Marker
|
<Marker
|
||||||
@@ -348,32 +363,40 @@ export class Home extends React.Component {
|
|||||||
sections={sections}
|
sections={sections}
|
||||||
stickySectionHeadersEnabled={true}
|
stickySectionHeadersEnabled={true}
|
||||||
renderSectionHeader={({ section: workItem }) => (
|
renderSectionHeader={({ section: workItem }) => (
|
||||||
<View
|
<TouchableHighlight
|
||||||
key={workItem._id}
|
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: "#F4F4F4",
|
|
||||||
paddingLeft: 8,
|
paddingLeft: 8,
|
||||||
height: 45,
|
height: 45,
|
||||||
}}>
|
backgroundColor: "#F4F4F4",
|
||||||
<Icon
|
}}
|
||||||
name={
|
underlayColor="#EEEEEE"
|
||||||
workItem.workItemType === "order"
|
onPress={() => this.handleSectionSelect(workItem)}>
|
||||||
? "hardhat"
|
<View
|
||||||
: workItem.workItemType === "complaint"
|
key={workItem._id}
|
||||||
? "question"
|
style={{
|
||||||
: "clipboard"
|
height: "100%",
|
||||||
}
|
width: "100%",
|
||||||
size={16}
|
flexDirection: "row",
|
||||||
style={{ marginRight: 10 }}
|
justifyContent: "flex-start",
|
||||||
/>
|
alignItems: "center",
|
||||||
<Text style={{ fontSize: 16 }}>
|
}}>
|
||||||
{workItemTypeText[workItem.workItemType].toUpperCase()}{" "}
|
<Icon
|
||||||
{pad(workItem.ticketNumber, 4)}
|
name={
|
||||||
</Text>
|
workItem.workItemType === "order"
|
||||||
</View>
|
? "hardhat"
|
||||||
|
: workItem.workItemType === "complaint"
|
||||||
|
? "question"
|
||||||
|
: "clipboard"
|
||||||
|
}
|
||||||
|
size={16}
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
/>
|
||||||
|
<Text style={{ fontSize: 16 }}>
|
||||||
|
{workItemTypeText[workItem.workItemType].toUpperCase()}{" "}
|
||||||
|
{pad(workItem.ticketNumber, 4)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableHighlight>
|
||||||
)}
|
)}
|
||||||
keyExtractor={(item) => item._id}
|
keyExtractor={(item) => item._id}
|
||||||
renderItem={({ item: activity, section }) => {
|
renderItem={({ item: activity, section }) => {
|
||||||
|
|||||||
@@ -18,11 +18,10 @@ import {
|
|||||||
BoundHeader,
|
BoundHeader,
|
||||||
Icon,
|
Icon,
|
||||||
Header,
|
Header,
|
||||||
PhotoButton,
|
|
||||||
BoundOptionStrip,
|
BoundOptionStrip,
|
||||||
PhotoPanel,
|
BoundPhotoPanel,
|
||||||
} from "../ui"
|
} from "../ui"
|
||||||
import { MessageModal } from "../Modal"
|
import { MessageModal, WaitModal } from "../Modal"
|
||||||
import autobind from "autobind-decorator"
|
import autobind from "autobind-decorator"
|
||||||
import { ifIphoneX, isIphoneX } from "react-native-iphone-x-helper"
|
import { ifIphoneX, isIphoneX } from "react-native-iphone-x-helper"
|
||||||
import KeyboardSpacer from "react-native-keyboard-spacer"
|
import KeyboardSpacer from "react-native-keyboard-spacer"
|
||||||
@@ -65,6 +64,9 @@ export class WorkItem extends React.Component {
|
|||||||
isValid: true,
|
isValid: true,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
},
|
},
|
||||||
|
photos: {
|
||||||
|
isValid: (r, v) => v && v.length > 0,
|
||||||
|
},
|
||||||
details: {
|
details: {
|
||||||
isValid: (r, v) => v !== "",
|
isValid: (r, v) => v !== "",
|
||||||
},
|
},
|
||||||
@@ -87,6 +89,7 @@ export class WorkItem extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
binder: new FormBinder({}, WorkItem.bindings),
|
binder: new FormBinder({}, WorkItem.bindings),
|
||||||
messageModal: null,
|
messageModal: null,
|
||||||
|
waitModal: null,
|
||||||
region,
|
region,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,8 +233,18 @@ export class WorkItem extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleUploadStarted() {
|
||||||
|
this.setState({ waitModal: { message: "Uploading Photo..." } })
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleUploadEnded() {
|
||||||
|
this.setState({ waitModal: null })
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { binder, messageModal, region } = this.state
|
const { binder, messageModal, waitModal, region } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
@@ -304,10 +317,19 @@ export class WorkItem extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.panel}>
|
<View style={styles.panel}>
|
||||||
<PhotoPanel />
|
<BoundPhotoPanel
|
||||||
|
name="photos"
|
||||||
|
binder={binder}
|
||||||
|
onUploadStarted={this.handleUploadStarted}
|
||||||
|
onUploadEnded={this.handleUploadEnded}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
{isIphoneX ? <View style={{ height: 30, width: "100%" }} /> : null}
|
{isIphoneX ? <View style={{ height: 30, width: "100%" }} /> : null}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
<WaitModal
|
||||||
|
open={!!waitModal}
|
||||||
|
message={waitModal ? waitModal.message : ""}
|
||||||
|
/>
|
||||||
<MessageModal
|
<MessageModal
|
||||||
open={!!messageModal}
|
open={!!messageModal}
|
||||||
icon={messageModal ? messageModal.icon : ""}
|
icon={messageModal ? messageModal.icon : ""}
|
||||||
|
|||||||
@@ -36,10 +36,7 @@ export class BoundInput extends React.Component {
|
|||||||
handleChangeText(newText) {
|
handleChangeText(newText) {
|
||||||
const { binder, name } = this.props
|
const { binder, name } = this.props
|
||||||
|
|
||||||
// TODO: Sometimes this is undefined and causes a crash?!
|
|
||||||
if (binder) {
|
if (binder) {
|
||||||
const state = binder.getFieldState(name)
|
|
||||||
|
|
||||||
this.setState(binder.updateFieldValue(name, newText))
|
this.setState(binder.updateFieldValue(name, newText))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
169
mobile/src/ui/BoundPhotoPanel.js
Normal file
169
mobile/src/ui/BoundPhotoPanel.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import React, { Component } from "react"
|
||||||
|
import PropTypes from "prop-types"
|
||||||
|
import {
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
Image,
|
||||||
|
TouchableOpacity,
|
||||||
|
Dimensions,
|
||||||
|
ActivityIndicator,
|
||||||
|
} from "react-native"
|
||||||
|
import { Icon } from "."
|
||||||
|
import ImagePicker from "react-native-image-picker"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
import { api } from "../API"
|
||||||
|
|
||||||
|
const getScreenPortraitDimensions = () => {
|
||||||
|
const { width, height } = Dimensions.get("window")
|
||||||
|
|
||||||
|
return {
|
||||||
|
screenWidth: Math.min(width, height),
|
||||||
|
screenHeight: Math.max(width, height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BoundPhotoPanel extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
onUploadStarted: PropTypes.func,
|
||||||
|
onUploadEnded: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
const { name, binder } = this.props
|
||||||
|
|
||||||
|
this.state = binder.getFieldState(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.binder !== this.props.binder) {
|
||||||
|
this.setState(nextProps.binder.getFieldState(nextProps.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handlePhotoPress(index) {
|
||||||
|
const { onUploadStarted, onUploadEnded } = this.props
|
||||||
|
|
||||||
|
ImagePicker.showImagePicker(
|
||||||
|
{
|
||||||
|
title: "Select Photo",
|
||||||
|
storageOptions: {
|
||||||
|
skipBackup: true,
|
||||||
|
path: "photos",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
if (!response.didCancel && !response.error) {
|
||||||
|
if (onUploadStarted) {
|
||||||
|
onUploadStarted()
|
||||||
|
}
|
||||||
|
api
|
||||||
|
.upload(response.data)
|
||||||
|
.then((uploadData) => {
|
||||||
|
if (onUploadEnded) {
|
||||||
|
onUploadEnded(true, uploadData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { binder, name } = this.props
|
||||||
|
|
||||||
|
if (binder) {
|
||||||
|
const value = binder.getFieldValue(name)
|
||||||
|
let newValue = value.slice(0)
|
||||||
|
|
||||||
|
newValue[index] = uploadData.assetId
|
||||||
|
|
||||||
|
this.setState(binder.updateFieldValue(name, newValue))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (onUploadEnded) {
|
||||||
|
onUploadEnded(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { screenWidth, screenHeight } = getScreenPortraitDimensions()
|
||||||
|
const photoWidth = screenHeight / 4
|
||||||
|
const photoHeight = screenWidth / 4
|
||||||
|
const rowPadding = 10
|
||||||
|
const { value: assetIds } = this.state
|
||||||
|
|
||||||
|
const renderPhoto = (index) => {
|
||||||
|
const assetId = assetIds[index]
|
||||||
|
|
||||||
|
if (assetId) {
|
||||||
|
console.log(api.makeImageUrl(assetId))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={assetId || "blank" + index.toString()}
|
||||||
|
style={{
|
||||||
|
width: photoWidth,
|
||||||
|
height: photoHeight,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: "gray",
|
||||||
|
borderRadius: 4,
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
onPress={() => this.handlePhotoPress(index)}>
|
||||||
|
{!assetId && (
|
||||||
|
<Icon name="add" size={24} style={{ alignSelf: "center" }} />
|
||||||
|
)}
|
||||||
|
{assetId && (
|
||||||
|
<Image
|
||||||
|
source={{ uri: api.makeImageUrl(assetId) }}
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const extraRowStyle = {
|
||||||
|
height: photoHeight + rowPadding,
|
||||||
|
paddingTop: rowPadding / 2,
|
||||||
|
paddingBottom: rowPadding / 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: 14,
|
||||||
|
marginBottom: 4,
|
||||||
|
}}>
|
||||||
|
Pictures:
|
||||||
|
</Text>
|
||||||
|
<View style={[styles.photoRow, extraRowStyle]}>
|
||||||
|
{renderPhoto(0)}
|
||||||
|
{renderPhoto(1)}
|
||||||
|
</View>
|
||||||
|
<View style={[styles.photoRow, extraRowStyle]}>
|
||||||
|
{renderPhoto(2)}
|
||||||
|
{renderPhoto(3)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
photoRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import React, { Component } from "react"
|
|
||||||
import {
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
Image,
|
|
||||||
TouchableOpacity,
|
|
||||||
Dimensions,
|
|
||||||
} from "react-native"
|
|
||||||
import { Icon } from "."
|
|
||||||
import ImagePicker from "react-native-image-picker"
|
|
||||||
import autobind from "autobind-decorator"
|
|
||||||
|
|
||||||
const getScreenPortraitDimensions = () => {
|
|
||||||
const { width, height } = Dimensions.get("window")
|
|
||||||
|
|
||||||
return {
|
|
||||||
screenWidth: Math.min(width, height),
|
|
||||||
screenHeight: Math.max(width, height),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PhotoPanel extends Component {
|
|
||||||
@autobind
|
|
||||||
handlePhotoPress() {
|
|
||||||
ImagePicker.showImagePicker(
|
|
||||||
{
|
|
||||||
title: "Select Photo",
|
|
||||||
storageOptions: {
|
|
||||||
skipBackup: true,
|
|
||||||
path: "photos",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(response) => {
|
|
||||||
console.log("Response = ", response)
|
|
||||||
|
|
||||||
if (response.didCancel) {
|
|
||||||
console.log("User cancelled image picker")
|
|
||||||
} else if (response.error) {
|
|
||||||
console.log("ImagePicker Error: ", response.error)
|
|
||||||
} else if (response.customButton) {
|
|
||||||
console.log("User tapped custom button: ", response.customButton)
|
|
||||||
} else {
|
|
||||||
// You can also display the image using data:
|
|
||||||
// let source = { uri: 'data:image/jpeg;base64,' + response.data };
|
|
||||||
console.log(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { screenWidth, screenHeight } = getScreenPortraitDimensions()
|
|
||||||
const photoWidth = screenHeight / 4
|
|
||||||
const photoHeight = screenWidth / 4
|
|
||||||
const numRows = 2
|
|
||||||
const numCols = 2
|
|
||||||
const rowPadding = 10
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontSize: 14,
|
|
||||||
marginBottom: 4,
|
|
||||||
}}>
|
|
||||||
Pictures:
|
|
||||||
</Text>
|
|
||||||
{Array.from(new Array(numRows), (x, i) => (
|
|
||||||
<View
|
|
||||||
key={"r" + i.toString()}
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
width: "100%",
|
|
||||||
height: photoHeight + rowPadding,
|
|
||||||
paddingTop: rowPadding / 2,
|
|
||||||
paddingBottom: rowPadding / 2,
|
|
||||||
}}>
|
|
||||||
{Array.from(new Array(numCols), (y, j) => (
|
|
||||||
<TouchableOpacity
|
|
||||||
key={"r" + i.toString() + "c" + j.toString()}
|
|
||||||
style={{
|
|
||||||
width: photoWidth,
|
|
||||||
height: photoHeight,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: "gray",
|
|
||||||
borderRadius: 4,
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
onPress={this.handlePhotoPress}>
|
|
||||||
<Icon name="add" size={24} style={{ alignSelf: "center" }} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
export { Icon } from "./Icon"
|
export { Icon } from "./Icon"
|
||||||
export { Header } from "./Header"
|
export { Header } from "./Header"
|
||||||
export { PhotoPanel } from "./PhotoPanel"
|
|
||||||
export { OptionStrip } from "./OptionStrip"
|
export { OptionStrip } from "./OptionStrip"
|
||||||
export { BoundSwitch } from "./BoundSwitch"
|
export { BoundSwitch } from "./BoundSwitch"
|
||||||
export { BoundInput } from "./BoundInput"
|
export { BoundInput } from "./BoundInput"
|
||||||
export { BoundButton } from "./BoundButton"
|
export { BoundButton } from "./BoundButton"
|
||||||
export { BoundOptionStrip } from "./BoundOptionStrip"
|
export { BoundOptionStrip } from "./BoundOptionStrip"
|
||||||
export { BoundHeader } from "./BoundHeader"
|
export { BoundHeader } from "./BoundHeader"
|
||||||
|
export { BoundPhotoPanel } from "./BoundPhotoPanel"
|
||||||
|
|||||||
@@ -65,7 +65,14 @@ export class AssetRoutes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getAsset(req, res, next) {
|
async getAsset(req, res, next) {
|
||||||
const assetId = req.params._id
|
let assetId = req.params._id
|
||||||
|
const extIndex = assetId.indexOf(".")
|
||||||
|
|
||||||
|
if (extIndex !== -1) {
|
||||||
|
// TODO: Should really check the index against the requested extension...
|
||||||
|
assetId = assetId.slice(0, extIndex)
|
||||||
|
}
|
||||||
|
|
||||||
const file = await this.db.gridfs.findOneAsync({ _id: assetId })
|
const file = await this.db.gridfs.findOneAsync({ _id: assetId })
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@@ -112,13 +119,17 @@ export class AssetRoutes {
|
|||||||
chunkContentType,
|
chunkContentType,
|
||||||
} = req.body
|
} = req.body
|
||||||
|
|
||||||
if (!fileName || !uploadSize || !numberOfChunks || !contentType) {
|
if (!uploadSize || !numberOfChunks || !contentType) {
|
||||||
throw createError.BadRequest(
|
throw createError.BadRequest(
|
||||||
"Must specify fileName, uploadSize, numberOfChunks, contentType"
|
"Must specify uploadSize, numberOfChunks, contentType"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName = uploadId + "-" + path.basename(fileName)
|
if (fileName) {
|
||||||
|
fileName = uploadId + "-" + path.basename(fileName)
|
||||||
|
} else {
|
||||||
|
fileName = uploadId
|
||||||
|
}
|
||||||
|
|
||||||
if (chunkContentType) {
|
if (chunkContentType) {
|
||||||
if (
|
if (
|
||||||
@@ -159,9 +170,11 @@ export class AssetRoutes {
|
|||||||
const contentRange = req.get("Content-Range")
|
const contentRange = req.get("Content-Range")
|
||||||
const contentLength = req.get("Content-Length")
|
const contentLength = req.get("Content-Length")
|
||||||
|
|
||||||
console.log(uploadData)
|
if (!uploadData) {
|
||||||
|
throw createError.BadRequest(`Bad upload id ${uploadId}`)
|
||||||
|
}
|
||||||
|
|
||||||
if (contentType !== uploadData.chunkContentType) {
|
if (!contentType.startsWith(uploadData.chunkContentType)) {
|
||||||
throw createError.BadRequest(
|
throw createError.BadRequest(
|
||||||
`Content-Type ${contentType} does not match chunk type ${
|
`Content-Type ${contentType} does not match chunk type ${
|
||||||
uploadData.chunkContentType
|
uploadData.chunkContentType
|
||||||
@@ -211,7 +224,7 @@ export class AssetRoutes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [uploadedChunks] = await Promise.all([
|
const [, uploadedChunks] = await Promise.all([
|
||||||
this.rs.setrangeAsync(uploadDataId, offset, req.body),
|
this.rs.setrangeAsync(uploadDataId, offset, req.body),
|
||||||
this.rs.incrAsync(uploadCountId),
|
this.rs.incrAsync(uploadCountId),
|
||||||
])
|
])
|
||||||
|
|||||||
Reference in New Issue
Block a user