Merge pull request #1924 from kchadha/camera-costume
Upload a costume from camera
This commit is contained in:
7 files changed
+457
-4
No files matched your search
@@ -0,0 +1,153 @@
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/units.css";
|
||||
|
||||
$main-button-size: 2.75rem;
|
||||
|
||||
.modal-content {
|
||||
width: 552px;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: $ui-white;
|
||||
padding: 1.5rem 2.25rem;
|
||||
}
|
||||
|
||||
.camera-feed-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
|
||||
background: $ui-primary;
|
||||
border: 1px solid $ui-black-transparent;
|
||||
border-radius: 4px;
|
||||
padding: 3px;
|
||||
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
position: absolute;
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
position: absolute;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: $text-primary-transparent;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
margin: 10px auto 0;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: $text-primary-transparent;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.capture-text {
|
||||
color: $motion-primary;
|
||||
}
|
||||
|
||||
.disabled-text {
|
||||
color: $text-primary;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.main-button-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin-top: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Action Menu */
|
||||
.main-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
background: $motion-primary;
|
||||
outline: none;
|
||||
border: none;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
border-radius: 100%;
|
||||
width: $main-button-size;
|
||||
height: $main-button-size;
|
||||
box-shadow: 0 0 0 4px $motion-transparent;
|
||||
}
|
||||
|
||||
.main-button:hover {
|
||||
background: $pen-primary;
|
||||
box-shadow: 0 0 0 6px $motion-transparent;
|
||||
}
|
||||
|
||||
.main-button:disabled {
|
||||
background: $text-primary;
|
||||
border-color: $ui-black-transparent;
|
||||
box-shadow: none;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.main-icon {
|
||||
width: calc($main-button-size - 1rem);
|
||||
height: calc($main-button-size - 1rem);
|
||||
}
|
||||
|
||||
.button-row {
|
||||
font-weight: bolder;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
.button-row button {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
background: $ui-white;
|
||||
border: 1px solid $ui-black-transparent;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
color: $motion-primary;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-row button.ok-button {
|
||||
background: $motion-primary;
|
||||
border: $motion-primary;
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.flash-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $ui-white;
|
||||
animation-name: flash;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards; /* Leave at 0 opacity after animation */
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
import Box from '../box/box.jsx';
|
||||
import Modal from '../modal/modal.jsx';
|
||||
import styles from './camera-modal.css';
|
||||
import backIcon from './icon--back.svg';
|
||||
import cameraIcon from '../action-menu/icon--camera.svg';
|
||||
|
||||
const messages = defineMessages({
|
||||
cameraModalTitle: {
|
||||
defaultMessage: 'Take a Photo',
|
||||
description: 'Title for prompt to take a picture (to add as a new costume).',
|
||||
id: 'gui.cameraModal.cameraModalTitle'
|
||||
},
|
||||
loadingCameraMessage: {
|
||||
defaultMessage: 'Loading Camera...',
|
||||
description: 'Notification to the user that the camera is loading',
|
||||
id: 'gui.cameraModal.loadingCameraMessage'
|
||||
},
|
||||
permissionRequest: {
|
||||
defaultMessage: 'We need your permission to use your camera',
|
||||
description: 'Notification to the user that the app needs camera access',
|
||||
id: 'gui.cameraModal.permissionRequest'
|
||||
},
|
||||
retakePhoto: {
|
||||
defaultMessage: 'Retake Photo',
|
||||
description: 'A button that allows the user to take the picture again, replacing the old one',
|
||||
id: 'gui.cameraModal.retakePhoto'
|
||||
},
|
||||
save: {
|
||||
defaultMessage: 'Save',
|
||||
description: 'A button that allows the user to save the photo they took as a costume',
|
||||
id: 'gui.cameraModal.save'
|
||||
},
|
||||
takePhotoButton: {
|
||||
defaultMessage: 'Take Photo',
|
||||
description: 'A button to take a photo',
|
||||
id: 'gui.cameraModal.takePhoto'
|
||||
},
|
||||
loadingCaption: {
|
||||
defaultMessage: 'Loading...',
|
||||
description: 'A caption for a disabled button while the video from the camera is still loading',
|
||||
id: 'gui.cameraModal.loadingCaption'
|
||||
},
|
||||
enableCameraCaption: {
|
||||
defaultMessage: 'Enable Camera',
|
||||
description: 'A caption for a disabled button prompting the user to enable camera access',
|
||||
id: 'gui.cameraModal.enableCameraCaption'
|
||||
}
|
||||
});
|
||||
|
||||
const CameraModal = ({intl, ...props}) => (
|
||||
<Modal
|
||||
className={styles.modalContent}
|
||||
contentLabel={intl.formatMessage(messages.cameraModalTitle)}
|
||||
onRequestClose={props.onCancel}
|
||||
>
|
||||
<Box className={styles.body}>
|
||||
<Box className={styles.cameraFeedContainer}>
|
||||
<div className={styles.loadingText}>
|
||||
{props.access ? intl.formatMessage(messages.loadingCameraMessage) :
|
||||
`↖️ \u00A0${intl.formatMessage(messages.permissionRequest)}`}
|
||||
</div>
|
||||
<canvas
|
||||
className={styles.canvas}
|
||||
// height and (below) width of the actual image
|
||||
// double stage dimensions to avoid the need for
|
||||
// resizing the captured image when importing costume
|
||||
// to accommodate double resolution bitmaps
|
||||
height="720"
|
||||
ref={props.canvasRef}
|
||||
width="960"
|
||||
/>
|
||||
{props.capture ? (
|
||||
<div className={styles.flashOverlay} />
|
||||
) : null}
|
||||
</Box>
|
||||
{props.capture ?
|
||||
<Box className={styles.buttonRow}>
|
||||
<button
|
||||
className={styles.cancelButton}
|
||||
key="retake-button"
|
||||
onClick={props.onBack}
|
||||
>
|
||||
<img
|
||||
draggable={false}
|
||||
src={backIcon}
|
||||
/> {intl.formatMessage(messages.retakePhoto)}
|
||||
</button>
|
||||
<button
|
||||
className={styles.okButton}
|
||||
onClick={props.onSubmit}
|
||||
> {intl.formatMessage(messages.save)}
|
||||
</button>
|
||||
</Box> :
|
||||
<Box className={styles.mainButtonRow}>
|
||||
<button
|
||||
className={styles.mainButton}
|
||||
disabled={!props.loaded}
|
||||
key="capture-button"
|
||||
onClick={props.onCapture}
|
||||
>
|
||||
<img
|
||||
className={styles.mainIcon}
|
||||
draggable={false}
|
||||
src={cameraIcon}
|
||||
/>
|
||||
</button>
|
||||
<div className={styles.helpText}>
|
||||
{props.access ?
|
||||
<span className={props.loaded ? styles.captureText : styles.disabledText}>
|
||||
{props.loaded ?
|
||||
intl.formatMessage(messages.takePhotoButton) :
|
||||
intl.formatMessage(messages.loadingCaption)}
|
||||
</span> :
|
||||
<span className={styles.disabledText}>
|
||||
{intl.formatMessage(messages.enableCameraCaption)}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
CameraModal.propTypes = {
|
||||
access: PropTypes.bool,
|
||||
canvasRef: PropTypes.func.isRequired,
|
||||
capture: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
loaded: PropTypes.bool,
|
||||
onBack: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onCapture: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(CameraModal);
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="8px" viewBox="0 0 18 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>re-record</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop---1280x720" transform="translate(-446.000000, -454.000000)" fill="#4C97FF">
|
||||
<g id="Step-4---Playing-Trim" transform="translate(0.000000, 42.000000)">
|
||||
<g id="Re-record" transform="translate(434.000000, 400.000000)">
|
||||
<path d="M23.6951773,18.0886272 L20.5085204,21.2806841 C20.2691162,21.5146883 19.9451104,21.6466907 19.6085044,21.6466907 C19.2730984,21.6466907 18.9490926,21.5146883 18.7084883,21.2806841 L15.5230315,18.0886272 C15.157025,17.7226207 15.049023,17.1766109 15.2470266,16.7026025 C15.4450301,16.228594 15.9010382,15.9225886 16.4170474,15.9225886 L17.5630679,15.9225886 C17.5330673,15.6465836 17.455066,15.3345781 17.3230636,15.0045722 C17.2816629,14.9085705 17.2390621,14.8125688 17.1910612,14.716567 C17.1250601,14.6085651 17.1316602,14.5785646 17.0410586,14.4465622 C16.897056,14.2305584 16.7650536,14.0685555 16.6084509,13.8825522 C16.2910452,13.5405461 15.9070383,13.2465408 15.4990311,13.030537 C15.0850237,12.8145331 14.653016,12.6825308 14.2690091,12.6105295 C13.8910024,12.5445283 13.5429962,12.5385282 13.3389925,12.5385282 C13.2369907,12.5325281 13.1049884,12.5565285 13.0389872,12.5625286 C12.9669859,12.5685287 12.9249852,12.5745288 12.9249852,12.5745288 C12.4989776,12.6165296 12.1149707,12.304524 12.07297,11.8785164 C12.0369693,11.51851 12.2469731,11.1945042 12.5649787,11.0745021 C12.5649787,11.0745021 12.6069795,11.0565018 12.6729807,11.0325013 C12.750982,11.0085009 12.8229833,10.9665001 12.9909863,10.9184993 C13.3269923,10.8164975 13.7529999,10.7084955 14.3110099,10.6544946 C14.8630197,10.6064937 15.5290316,10.6184939 16.2316441,10.7624965 C16.9330566,10.9124992 17.6710698,11.1945042 18.355082,11.6025115 C18.6790878,11.8125152 19.027094,12.0585196 19.2970988,12.2985239 C19.417101,12.3885255 19.6211046,12.5925292 19.7411067,12.7185314 C19.8791092,12.862534 20.0051114,13.0065365 20.1317137,13.1565392 C20.6171224,13.7565499 20.9771288,14.4045615 21.211133,14.992572 C21.3491354,15.328578 21.4451371,15.6465836 21.5171384,15.9225886 L22.8011613,15.9225886 C23.3171705,15.9225886 23.7731787,16.228594 23.9711822,16.7026025 C24.1691857,17.1766109 24.0611838,17.7226207 23.6951773,18.0886272" id="re-record" transform="translate(18.068963, 16.137926) scale(-1, 1) rotate(-45.000000) translate(-18.068963, -16.137926) "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
Reference in new issue
Block a user