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 |
@@ -0,0 +1,103 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import CameraModalComponent from '../components/camera-modal/camera-modal.jsx';
|
||||
import ModalVideoManager from '../lib/video/modal-video-manager.js';
|
||||
|
||||
import {
|
||||
closeCameraCapture
|
||||
} from '../reducers/modals';
|
||||
|
||||
class CameraModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleAccess',
|
||||
'handleBack',
|
||||
'handleCancel',
|
||||
'handleCapture',
|
||||
'handleLoaded',
|
||||
'handleSubmit',
|
||||
'setCanvas'
|
||||
]);
|
||||
|
||||
this.video = null;
|
||||
this.videoDevice = null;
|
||||
|
||||
this.state = {
|
||||
capture: null,
|
||||
access: false,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
componentWillUnmount () {
|
||||
if (this.videoDevice) {
|
||||
this.videoDevice.disableVideo();
|
||||
}
|
||||
}
|
||||
handleAccess () {
|
||||
this.setState({access: true});
|
||||
}
|
||||
handleLoaded () {
|
||||
this.setState({loaded: true});
|
||||
}
|
||||
handleBack () {
|
||||
this.setState({capture: null});
|
||||
this.videoDevice.clearSnapshot();
|
||||
}
|
||||
handleCapture () {
|
||||
if (this.state.loaded) {
|
||||
const capture = this.videoDevice.takeSnapshot();
|
||||
this.setState({capture: capture});
|
||||
}
|
||||
}
|
||||
setCanvas (canvas) {
|
||||
this.canvas = canvas;
|
||||
if (this.canvas) {
|
||||
this.videoDevice = new ModalVideoManager(this.canvas);
|
||||
this.videoDevice.enableVideo(this.handleAccess, this.handleLoaded);
|
||||
}
|
||||
}
|
||||
handleSubmit () {
|
||||
if (!this.state.capture) return;
|
||||
this.props.onNewCostume(this.state.capture);
|
||||
this.props.onClose();
|
||||
}
|
||||
handleCancel () {
|
||||
this.props.onClose();
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<CameraModalComponent
|
||||
access={this.state.access}
|
||||
canvasRef={this.setCanvas}
|
||||
capture={this.state.capture}
|
||||
loaded={this.state.loaded}
|
||||
onBack={this.handleBack}
|
||||
onCancel={this.handleCancel}
|
||||
onCapture={this.handleCapture}
|
||||
onSubmit={this.handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CameraModal.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
onNewCostume: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = () => ({});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onClose: () => {
|
||||
dispatch(closeCameraCapture());
|
||||
}
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CameraModal);
|
||||
@@ -8,12 +8,15 @@ import AssetPanel from '../components/asset-panel/asset-panel.jsx';
|
||||
import PaintEditorWrapper from './paint-editor-wrapper.jsx';
|
||||
import CostumeLibrary from './costume-library.jsx';
|
||||
import BackdropLibrary from './backdrop-library.jsx';
|
||||
import CameraModal from './camera-modal.jsx';
|
||||
import {connect} from 'react-redux';
|
||||
import {handleFileUpload, costumeUpload} from '../lib/file-uploader.js';
|
||||
|
||||
import {
|
||||
closeCameraCapture,
|
||||
closeCostumeLibrary,
|
||||
closeBackdropLibrary,
|
||||
openCameraCapture,
|
||||
openCostumeLibrary,
|
||||
openBackdropLibrary
|
||||
} from '../reducers/modals';
|
||||
@@ -60,7 +63,7 @@ const messages = defineMessages({
|
||||
id: 'gui.costumeTab.addFileCostume'
|
||||
},
|
||||
addCameraCostumeMsg: {
|
||||
defaultMessage: 'Coming Soon',
|
||||
defaultMessage: 'Camera',
|
||||
description: 'Button to use the camera to create a costume costume in the editor tab',
|
||||
id: 'gui.costumeTab.addCameraCostume'
|
||||
}
|
||||
@@ -79,6 +82,7 @@ class CostumeTab extends React.Component {
|
||||
'handleSurpriseBackdrop',
|
||||
'handleFileUploadClick',
|
||||
'handleCostumeUpload',
|
||||
'handleCameraBuffer',
|
||||
'setFileInput'
|
||||
]);
|
||||
const {
|
||||
@@ -180,6 +184,10 @@ class CostumeTab extends React.Component {
|
||||
costumeUpload(buffer, fileType, fileName, storage, this.handleNewCostume);
|
||||
});
|
||||
}
|
||||
handleCameraBuffer (buffer) {
|
||||
const storage = this.props.vm.runtime.storage;
|
||||
costumeUpload(buffer, 'image/png', 'costume1', storage, this.handleNewCostume);
|
||||
}
|
||||
handleFileUploadClick () {
|
||||
this.fileInput.click();
|
||||
}
|
||||
@@ -194,11 +202,14 @@ class CostumeTab extends React.Component {
|
||||
render () {
|
||||
const {
|
||||
intl,
|
||||
onNewCostumeFromCameraClick,
|
||||
onNewLibraryBackdropClick,
|
||||
onNewLibraryCostumeClick,
|
||||
backdropLibraryVisible,
|
||||
cameraModalVisible,
|
||||
costumeLibraryVisible,
|
||||
onRequestCloseBackdropLibrary,
|
||||
onRequestCloseCameraModal,
|
||||
onRequestCloseCostumeLibrary,
|
||||
editingTarget,
|
||||
sprites,
|
||||
@@ -234,13 +245,14 @@ class CostumeTab extends React.Component {
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage(messages.addCameraCostumeMsg),
|
||||
img: cameraIcon
|
||||
img: cameraIcon,
|
||||
onClick: onNewCostumeFromCameraClick
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage(addFileMessage),
|
||||
img: fileUploadIcon,
|
||||
onClick: this.handleFileUploadClick,
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg', // coming soon
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg',
|
||||
fileChange: this.handleCostumeUpload,
|
||||
fileInput: this.setFileInput
|
||||
},
|
||||
@@ -280,6 +292,12 @@ class CostumeTab extends React.Component {
|
||||
onRequestClose={onRequestCloseBackdropLibrary}
|
||||
/>
|
||||
) : null}
|
||||
{cameraModalVisible ? (
|
||||
<CameraModal
|
||||
onClose={onRequestCloseCameraModal}
|
||||
onNewCostume={this.handleCameraBuffer}
|
||||
/>
|
||||
) : null}
|
||||
</AssetPanel>
|
||||
);
|
||||
}
|
||||
@@ -287,12 +305,15 @@ class CostumeTab extends React.Component {
|
||||
|
||||
CostumeTab.propTypes = {
|
||||
backdropLibraryVisible: PropTypes.bool,
|
||||
cameraModalVisible: PropTypes.bool,
|
||||
costumeLibraryVisible: PropTypes.bool,
|
||||
editingTarget: PropTypes.string,
|
||||
intl: intlShape,
|
||||
onNewCostumeFromCameraClick: PropTypes.func.isRequired,
|
||||
onNewLibraryBackdropClick: PropTypes.func.isRequired,
|
||||
onNewLibraryCostumeClick: PropTypes.func.isRequired,
|
||||
onRequestCloseBackdropLibrary: PropTypes.func.isRequired,
|
||||
onRequestCloseCameraModal: PropTypes.func.isRequired,
|
||||
onRequestCloseCostumeLibrary: PropTypes.func.isRequired,
|
||||
sprites: PropTypes.shape({
|
||||
id: PropTypes.shape({
|
||||
@@ -315,6 +336,7 @@ const mapStateToProps = state => ({
|
||||
editingTarget: state.targets.editingTarget,
|
||||
sprites: state.targets.sprites,
|
||||
stage: state.targets.stage,
|
||||
cameraModalVisible: state.modals.cameraCapture,
|
||||
costumeLibraryVisible: state.modals.costumeLibrary,
|
||||
backdropLibraryVisible: state.modals.backdropLibrary
|
||||
});
|
||||
@@ -328,11 +350,17 @@ const mapDispatchToProps = dispatch => ({
|
||||
e.preventDefault();
|
||||
dispatch(openCostumeLibrary());
|
||||
},
|
||||
onNewCostumeFromCameraClick: () => {
|
||||
dispatch(openCameraCapture());
|
||||
},
|
||||
onRequestCloseBackdropLibrary: () => {
|
||||
dispatch(closeBackdropLibrary());
|
||||
},
|
||||
onRequestCloseCostumeLibrary: () => {
|
||||
dispatch(closeCostumeLibrary());
|
||||
},
|
||||
onRequestCloseCameraModal: () => {
|
||||
dispatch(closeCameraCapture());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -77,7 +77,8 @@ const cacheAsset = function (storage, fileName, assetType, dataFormat, data) {
|
||||
|
||||
/**
|
||||
* Handles loading a costume or a backdrop using the provided, context-relevant information.
|
||||
* @param {ArrayBuffer} fileData The costume data to load
|
||||
* @param {ArrayBuffer | string} fileData The costume data to load (this can be an image url
|
||||
* iff the image is a bitmap)
|
||||
* @param {string} fileType The MIME type of this file
|
||||
* @param {string} costumeName The user-readable name to use for the costume.
|
||||
* @param {ScratchStorage} storage The ScratchStorage instance to cache the costume data
|
||||
|
||||
@@ -4,6 +4,7 @@ const OPEN_MODAL = 'scratch-gui/modals/OPEN_MODAL';
|
||||
const CLOSE_MODAL = 'scratch-gui/modals/CLOSE_MODAL';
|
||||
|
||||
const MODAL_BACKDROP_LIBRARY = 'backdropLibrary';
|
||||
const MODAL_CAMERA_CAPTURE = 'cameraCapture';
|
||||
const MODAL_COSTUME_LIBRARY = 'costumeLibrary';
|
||||
const MODAL_EXTENSION_LIBRARY = 'extensionLibrary';
|
||||
const MODAL_IMPORT_INFO = 'importInfo';
|
||||
@@ -18,6 +19,7 @@ const MODAL_TIPS_LIBRARY = 'tipsLibrary';
|
||||
|
||||
const initialState = {
|
||||
[MODAL_BACKDROP_LIBRARY]: false,
|
||||
[MODAL_CAMERA_CAPTURE]: false,
|
||||
[MODAL_COSTUME_LIBRARY]: false,
|
||||
[MODAL_EXTENSION_LIBRARY]: false,
|
||||
[MODAL_IMPORT_INFO]: false,
|
||||
@@ -60,6 +62,10 @@ const openBackdropLibrary = function () {
|
||||
analytics.pageview('/libraries/backdrops');
|
||||
return openModal(MODAL_BACKDROP_LIBRARY);
|
||||
};
|
||||
const openCameraCapture = function () {
|
||||
analytics.pageview('/modals/camera');
|
||||
return openModal(MODAL_CAMERA_CAPTURE);
|
||||
};
|
||||
const openCostumeLibrary = function () {
|
||||
analytics.pageview('/libraries/costumes');
|
||||
return openModal(MODAL_COSTUME_LIBRARY);
|
||||
@@ -99,6 +105,9 @@ const openTipsLibrary = function () {
|
||||
const closeBackdropLibrary = function () {
|
||||
return closeModal(MODAL_BACKDROP_LIBRARY);
|
||||
};
|
||||
const closeCameraCapture = function () {
|
||||
return closeModal(MODAL_CAMERA_CAPTURE);
|
||||
};
|
||||
const closeCostumeLibrary = function () {
|
||||
return closeModal(MODAL_COSTUME_LIBRARY);
|
||||
};
|
||||
@@ -129,6 +138,7 @@ const closeTipsLibrary = function () {
|
||||
export {
|
||||
reducer as default,
|
||||
openBackdropLibrary,
|
||||
openCameraCapture,
|
||||
openCostumeLibrary,
|
||||
openExtensionLibrary,
|
||||
openImportInfo,
|
||||
@@ -139,6 +149,7 @@ export {
|
||||
openSoundRecorder,
|
||||
openTipsLibrary,
|
||||
closeBackdropLibrary,
|
||||
closeCameraCapture,
|
||||
closeCostumeLibrary,
|
||||
closeExtensionLibrary,
|
||||
closeImportInfo,
|
||||
|
||||
Reference in new issue
Block a user