Create Asset List View and refactor overlay code (#356)
* start to create asset list * begin refactoring overlay component to remove duplicate code * refactoring of overlays, asset list styles * changes to add size to asset list * fixes to asset list * handle case in which a user hasn't uploaded any assets * fix bug in which asset list only grabbed first asset * remove console.log * update overlay exit styling to use icon mixin
This commit is contained in:
parent
080e9aa823
commit
e140702784
30 changed files with 716 additions and 502 deletions
|
@ -142,6 +142,11 @@ class Nav extends React.PureComponent {
|
|||
My sketches
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={`/${this.props.user.username}/assets`}>
|
||||
My assets
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={`/${this.props.user.username}/account`}>
|
||||
My account
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// TODO Organize this file by reducer type, ot break this apart into
|
||||
// multiple files
|
||||
export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
|
||||
export const TOGGLE_SKETCH = 'TOGGLE_SKETCH';
|
||||
|
||||
|
@ -124,3 +126,4 @@ export const CLEAR_PERSISTED_STATE = 'CLEAR_PERSISTED_STATE';
|
|||
|
||||
export const SHOW_HELP_MODAL = 'SHOW_HELP_MODAL';
|
||||
export const HIDE_HELP_MODAL = 'HIDE_HELP_MODAL';
|
||||
export const SET_ASSETS = 'SET_ASSETS';
|
||||
|
|
|
@ -1,21 +1,70 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import { browserHistory } from 'react-router';
|
||||
|
||||
function Overlay(props) {
|
||||
return (
|
||||
<div className="overlay">
|
||||
<div className="overlay-content">
|
||||
{props.children}
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
class Overlay extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.close = this.close.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.overlay.focus();
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this.props.closeOverlay) {
|
||||
browserHistory.push(this.props.previousPath);
|
||||
} else {
|
||||
this.props.closeOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
ariaLabel,
|
||||
title,
|
||||
children
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="overlay">
|
||||
<div className="overlay__content">
|
||||
<section
|
||||
tabIndex="0"
|
||||
role="main"
|
||||
aria-label={ariaLabel}
|
||||
ref={(element) => { this.overlay = element; }}
|
||||
className="overlay__body"
|
||||
>
|
||||
<header className="overlay__header">
|
||||
<h2 className="overlay__title">{title}</h2>
|
||||
<button className="overlay__close-button" onClick={this.close}>
|
||||
<InlineSVG src={exitUrl} alt="close overlay" />
|
||||
</button>
|
||||
</header>
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Overlay.propTypes = {
|
||||
children: PropTypes.element
|
||||
children: PropTypes.element,
|
||||
closeOverlay: PropTypes.func,
|
||||
title: PropTypes.string,
|
||||
ariaLabel: PropTypes.string,
|
||||
previousPath: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
Overlay.defaultProps = {
|
||||
children: null
|
||||
children: null,
|
||||
title: 'Modal',
|
||||
closeOverlay: null,
|
||||
ariaLabel: 'modal'
|
||||
};
|
||||
|
||||
export default Overlay;
|
||||
|
|
30
client/modules/IDE/actions/assets.js
Normal file
30
client/modules/IDE/actions/assets.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import axios from 'axios';
|
||||
|
||||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
const ROOT_URL = process.env.API_URL;
|
||||
|
||||
function setAssets(assets) {
|
||||
return {
|
||||
type: ActionTypes.SET_ASSETS,
|
||||
assets
|
||||
};
|
||||
}
|
||||
|
||||
export function getAssets(username) {
|
||||
return (dispatch, getState) => {
|
||||
axios.get(`${ROOT_URL}/S3/${username}/objects`, { withCredentials: true })
|
||||
.then((response) => {
|
||||
dispatch(setAssets(response.data.assets));
|
||||
})
|
||||
.catch(response => dispatch({
|
||||
type: ActionTypes.ERROR
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteAsset(assetKey, userId) {
|
||||
return {
|
||||
type: 'PLACEHOLDER'
|
||||
};
|
||||
}
|
|
@ -1,131 +1,101 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import React from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import { browserHistory } from 'react-router';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
const squareLogoUrl = require('../../../images/p5js-square-logo.svg');
|
||||
const playUrl = require('../../../images/play.svg');
|
||||
const asteriskUrl = require('../../../images/p5-asterisk.svg');
|
||||
|
||||
class About extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.closeAboutModal = this.closeAboutModal.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.aboutSection.focus();
|
||||
}
|
||||
|
||||
closeAboutModal() {
|
||||
browserHistory.push(this.props.previousPath);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<section className="about" ref={(element) => { this.aboutSection = element; }} tabIndex="0">
|
||||
<header className="about__header">
|
||||
<h2 className="about__header-title">Welcome</h2>
|
||||
<button className="about__exit-button" onClick={this.closeAboutModal}>
|
||||
<InlineSVG src={exitUrl} alt="Close About Overlay" />
|
||||
</button>
|
||||
</header>
|
||||
<div className="about__content">
|
||||
<div className="about__content-column">
|
||||
<InlineSVG className="about__logo" src={squareLogoUrl} alt="p5js Square Logo" />
|
||||
<p className="about__play-video">
|
||||
<a
|
||||
href="http://hello.p5js.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__play-video-button" src={playUrl} alt="Play Hello Video" />
|
||||
Play hello! video</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="about__content-column">
|
||||
<h3 className="about__content-column-title">New to p5.js?</h3>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://p5js.org/examples/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Examples</a>
|
||||
</p>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://p5js.org/tutorials/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Tutorials</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="about__content-column">
|
||||
<h3 className="about__content-column-title">Resources</h3>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://p5js.org/libraries/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Libraries</a>
|
||||
</p>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://p5js.org/reference/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Reference</a>
|
||||
</p>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://forum.processing.org/two/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Forum</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="about__footer">
|
||||
<p className="about__footer-list">
|
||||
<a
|
||||
href="https://github.com/processing/p5.js-web-editor"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Contribute</a>
|
||||
</p>
|
||||
<p className="about__footer-list">
|
||||
<a
|
||||
href="https://github.com/processing/p5.js-web-editor/issues/new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Report a bug</a>
|
||||
</p>
|
||||
<p className="about__footer-list">
|
||||
<a
|
||||
href="https://twitter.com/p5xjs?lang=en"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Twitter</a>
|
||||
</p>
|
||||
<button className="about__ok-button" onClick={this.closeAboutModal}>OK!</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
function About(props) {
|
||||
return (
|
||||
<div className="about__content">
|
||||
<div className="about__content-column">
|
||||
<InlineSVG className="about__logo" src={squareLogoUrl} alt="p5js Square Logo" />
|
||||
<p className="about__play-video">
|
||||
<a
|
||||
href="http://hello.p5js.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__play-video-button" src={playUrl} alt="Play Hello Video" />
|
||||
Play hello! video</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="about__content-column">
|
||||
<h3 className="about__content-column-title">New to p5.js?</h3>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://p5js.org/examples/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Examples</a>
|
||||
</p>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://p5js.org/tutorials/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Tutorials</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="about__content-column">
|
||||
<h3 className="about__content-column-title">Resources</h3>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://p5js.org/libraries/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Libraries</a>
|
||||
</p>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://p5js.org/reference/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Reference</a>
|
||||
</p>
|
||||
<p className="about__content-column-list">
|
||||
<a
|
||||
href="https://forum.processing.org/two/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Forum</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="about__footer">
|
||||
<p className="about__footer-list">
|
||||
<a
|
||||
href="https://github.com/processing/p5.js-web-editor"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Contribute</a>
|
||||
</p>
|
||||
<p className="about__footer-list">
|
||||
<a
|
||||
href="https://github.com/processing/p5.js-web-editor/issues/new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Report a bug</a>
|
||||
</p>
|
||||
<p className="about__footer-list">
|
||||
<a
|
||||
href="https://twitter.com/p5xjs?lang=en"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Twitter</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
About.propTypes = {
|
||||
previousPath: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default About;
|
||||
|
|
71
client/modules/IDE/components/AssetList.jsx
Normal file
71
client/modules/IDE/components/AssetList.jsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Link } from 'react-router';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
import * as AssetActions from '../actions/assets';
|
||||
|
||||
|
||||
class AssetList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.props.getAssets(this.props.username);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="asset-table-container">
|
||||
{this.props.assets.length === 0 &&
|
||||
<p className="asset-table__empty">No uploaded assets.</p>
|
||||
}
|
||||
{this.props.assets.length > 0 &&
|
||||
<table className="asset-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>View</th>
|
||||
<th>Sketch</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.assets.map(asset =>
|
||||
<tr className="asset-table__row" key={asset.key}>
|
||||
<td>{asset.name}</td>
|
||||
<td>{prettyBytes(asset.size)}</td>
|
||||
<td><Link to={asset.url} target="_blank">View</Link></td>
|
||||
<td><Link to={`/${this.props.username}/sketches/${asset.sketchId}`}>{asset.sketchName}</Link></td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AssetList.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
assets: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
sketchName: PropTypes.string.isRequired,
|
||||
sketchId: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
getAssets: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
user: state.user,
|
||||
assets: state.assets
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(Object.assign({}, AssetActions), dispatch);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AssetList);
|
|
@ -1,15 +1,7 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
class ErrorModal extends React.Component {
|
||||
componentDidMount() {
|
||||
this.errorModal.focus();
|
||||
}
|
||||
|
||||
|
||||
forceAuthentication() {
|
||||
return (
|
||||
<p>
|
||||
|
@ -40,25 +32,17 @@ class ErrorModal extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<section className="error-modal" ref={(element) => { this.errorModal = element; }} tabIndex="0">
|
||||
<header className="error-modal__header">
|
||||
<h2 className="error-modal__title">Error</h2>
|
||||
<button className="error-modal__exit-button" onClick={this.props.closeModal}>
|
||||
<InlineSVG src={exitUrl} alt="Close Error Modal" />
|
||||
</button>
|
||||
</header>
|
||||
<div className="error-modal__content">
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.type === 'forceAuthentication') {
|
||||
return this.forceAuthentication();
|
||||
} else if (this.props.type === 'staleSession') {
|
||||
return this.staleSession();
|
||||
} else if (this.props.type === 'staleProject') {
|
||||
return this.staleProject();
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
</section>
|
||||
<div className="error-modal__content">
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.type === 'forceAuthentication') {
|
||||
return this.forceAuthentication();
|
||||
} else if (this.props.type === 'staleSession') {
|
||||
return this.staleSession();
|
||||
} else if (this.props.type === 'staleProject') {
|
||||
return this.staleProject();
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,105 +1,84 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
metaKeyName,
|
||||
} from '../../../utils/metaKey';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
class KeyboardShortcutModal extends React.Component {
|
||||
componentDidMount() {
|
||||
this.isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<section className="keyboard-shortcuts">
|
||||
<header className="keyboard-shortcuts__header">
|
||||
<h2>Keyboard Shortcuts</h2>
|
||||
<button className="keyboard-shortcuts__close" onClick={this.props.closeModal}>
|
||||
<InlineSVG src={exitUrl} alt="Close Keyboard Shortcuts Overlay" />
|
||||
</button>
|
||||
</header>
|
||||
<ul title="keyboard shortcuts">
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">Shift + Tab</span>
|
||||
<span>Tidy</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + S
|
||||
</span>
|
||||
<span>Save</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + F
|
||||
</span>
|
||||
<span>Find Text</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + G
|
||||
</span>
|
||||
<span>Find Next Text Match</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Shift + G
|
||||
</span>
|
||||
<span>Find Previous Text Match</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + [
|
||||
</span>
|
||||
<span>Indent Code Left</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + ]
|
||||
</span>
|
||||
<span>Indent Code Right</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + /
|
||||
</span>
|
||||
<span>Comment Line</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Enter
|
||||
</span>
|
||||
<span>Start Sketch</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Shift + Enter
|
||||
</span>
|
||||
<span>Stop Sketch</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Shift + 1
|
||||
</span>
|
||||
<span>Toggle Text-based Canvas</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Shift + 2
|
||||
</span>
|
||||
<span>Turn Off Text-based Canvas</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
function KeyboardShortcutModal() {
|
||||
return (
|
||||
<ul className="keyboard-shortcuts" title="keyboard shortcuts">
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">Shift + Tab</span>
|
||||
<span>Tidy</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + S
|
||||
</span>
|
||||
<span>Save</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + F
|
||||
</span>
|
||||
<span>Find Text</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + G
|
||||
</span>
|
||||
<span>Find Next Text Match</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Shift + G
|
||||
</span>
|
||||
<span>Find Previous Text Match</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + [
|
||||
</span>
|
||||
<span>Indent Code Left</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + ]
|
||||
</span>
|
||||
<span>Indent Code Right</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + /
|
||||
</span>
|
||||
<span>Comment Line</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Enter
|
||||
</span>
|
||||
<span>Start Sketch</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Shift + Enter
|
||||
</span>
|
||||
<span>Stop Sketch</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Shift + 1
|
||||
</span>
|
||||
<span>Toggle Text-based Canvas</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Shift + 2
|
||||
</span>
|
||||
<span>Turn Off Text-based Canvas</span>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
KeyboardShortcutModal.propTypes = {
|
||||
closeModal: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default KeyboardShortcutModal;
|
||||
|
|
|
@ -1,57 +1,46 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
class ShareModal extends React.Component {
|
||||
componentDidMount() {
|
||||
this.shareModal.focus();
|
||||
}
|
||||
render() {
|
||||
const hostname = window.location.origin;
|
||||
return (
|
||||
<section className="share-modal" ref={(element) => { this.shareModal = element; }} tabIndex="0">
|
||||
<header className="share-modal__header">
|
||||
<h2>Share Sketch</h2>
|
||||
<button className="about__exit-button" onClick={this.props.closeShareModal}>
|
||||
<InlineSVG src={exitUrl} alt="Close Share Overlay" />
|
||||
</button>
|
||||
</header>
|
||||
<div className="share-modal__section">
|
||||
<label className="share-modal__label" htmlFor="share-modal__embed">Embed</label>
|
||||
<input
|
||||
type="text"
|
||||
className="share-modal__input"
|
||||
id="share-modal__embed"
|
||||
value={`<iframe src="${hostname}/embed/${this.props.projectId}"></iframe>`}
|
||||
/>
|
||||
</div>
|
||||
<div className="share-modal__section">
|
||||
<label className="share-modal__label" htmlFor="share-modal__fullscreen">Fullscreen</label>
|
||||
<input
|
||||
type="text"
|
||||
className="share-modal__input"
|
||||
id="share-modal__fullscreen"
|
||||
value={`${hostname}/full/${this.props.projectId}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="share-modal__section">
|
||||
<label className="share-modal__label" htmlFor="share-modal__edit">Edit</label>
|
||||
<input
|
||||
type="text"
|
||||
className="share-modal__input"
|
||||
id="share-modal__edit"
|
||||
value={`${hostname}/${this.props.ownerUsername}/sketches/${this.props.projectId}`}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
function ShareModal(props) {
|
||||
const {
|
||||
projectId,
|
||||
ownerUsername
|
||||
} = props;
|
||||
const hostname = window.location.origin;
|
||||
return (
|
||||
<div className="share-modal">
|
||||
<div className="share-modal__section">
|
||||
<label className="share-modal__label" htmlFor="share-modal__embed">Embed</label>
|
||||
<input
|
||||
type="text"
|
||||
className="share-modal__input"
|
||||
id="share-modal__embed"
|
||||
value={`<iframe src="${hostname}/embed/${projectId}"></iframe>`}
|
||||
/>
|
||||
</div>
|
||||
<div className="share-modal__section">
|
||||
<label className="share-modal__label" htmlFor="share-modal__fullscreen">Fullscreen</label>
|
||||
<input
|
||||
type="text"
|
||||
className="share-modal__input"
|
||||
id="share-modal__fullscreen"
|
||||
value={`${hostname}/full/${projectId}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="share-modal__section">
|
||||
<label className="share-modal__label" htmlFor="share-modal__edit">Edit</label>
|
||||
<input
|
||||
type="text"
|
||||
className="share-modal__input"
|
||||
id="share-modal__edit"
|
||||
value={`${hostname}/${ownerUsername}/sketches/${projectId}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ShareModal.propTypes = {
|
||||
projectId: PropTypes.string.isRequired,
|
||||
closeShareModal: PropTypes.func.isRequired,
|
||||
ownerUsername: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -8,80 +8,62 @@ import * as SketchActions from '../actions/projects';
|
|||
import * as ProjectActions from '../actions/project';
|
||||
import * as ToastActions from '../actions/toast';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
const trashCan = require('../../../images/trash-can.svg');
|
||||
|
||||
class SketchList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.closeSketchList = this.closeSketchList.bind(this);
|
||||
this.props.getProjects(this.props.username);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.getElementById('sketchlist').focus();
|
||||
}
|
||||
|
||||
closeSketchList() {
|
||||
browserHistory.push(this.props.previousPath);
|
||||
}
|
||||
|
||||
render() {
|
||||
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
|
||||
return (
|
||||
<section className="sketch-list" aria-label="project list" tabIndex="0" role="main" id="sketchlist">
|
||||
<header className="sketch-list__header">
|
||||
<h2 className="sketch-list__header-title">Open a Sketch</h2>
|
||||
<button className="sketch-list__exit-button" onClick={this.closeSketchList}>
|
||||
<InlineSVG src={exitUrl} alt="Close Sketch List Overlay" />
|
||||
</button>
|
||||
</header>
|
||||
<div className="sketches-table-container">
|
||||
<table className="sketches-table" summary="table containing all saved projects">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="sketch-list__trash-column" scope="col"></th>
|
||||
<th scope="col">Sketch</th>
|
||||
<th scope="col">Date created</th>
|
||||
<th scope="col">Date updated</th>
|
||||
<div className="sketches-table-container">
|
||||
<table className="sketches-table" summary="table containing all saved projects">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="sketch-list__trash-column" scope="col"></th>
|
||||
<th scope="col">Sketch</th>
|
||||
<th scope="col">Date created</th>
|
||||
<th scope="col">Date updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.sketches.map(sketch =>
|
||||
// eslint-disable-next-line
|
||||
<tr
|
||||
className="sketches-table__row visibility-toggle"
|
||||
key={sketch.id}
|
||||
onClick={() => browserHistory.push(`/${username}/sketches/${sketch.id}`)}
|
||||
>
|
||||
<td className="sketch-list__trash-column">
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.username === this.props.user.username || this.props.username === undefined) {
|
||||
return (
|
||||
<button
|
||||
className="sketch-list__trash-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (window.confirm(`Are you sure you want to delete "${sketch.name}"?`)) {
|
||||
this.props.deleteProject(sketch.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<InlineSVG src={trashCan} alt="Delete Project" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
</td>
|
||||
<th scope="row"><Link to={`/${username}/sketches/${sketch.id}`}>{sketch.name}</Link></th>
|
||||
<td>{moment(sketch.createdAt).format('MMM D, YYYY h:mm A')}</td>
|
||||
<td>{moment(sketch.updatedAt).format('MMM D, YYYY h:mm A')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.sketches.map(sketch =>
|
||||
// eslint-disable-next-line
|
||||
<tr
|
||||
className="sketches-table__row visibility-toggle"
|
||||
key={sketch.id}
|
||||
onClick={() => browserHistory.push(`/${username}/sketches/${sketch.id}`)}
|
||||
>
|
||||
<td className="sketch-list__trash-column">
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.username === this.props.user.username || this.props.username === undefined) {
|
||||
return (
|
||||
<button
|
||||
className="sketch-list__trash-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (window.confirm(`Are you sure you want to delete "${sketch.name}"?`)) {
|
||||
this.props.deleteProject(sketch.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<InlineSVG src={trashCan} alt="Delete Project" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
</td>
|
||||
<th scope="row"><Link to={`/${username}/sketches/${sketch.id}`}>{sketch.name}</Link></th>
|
||||
<td>{moment(sketch.createdAt).format('MMM D, YYYY h:mm A')}</td>
|
||||
<td>{moment(sketch.updatedAt).format('MMM D, YYYY h:mm A')}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -98,8 +80,7 @@ SketchList.propTypes = {
|
|||
updatedAt: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
username: PropTypes.string,
|
||||
deleteProject: PropTypes.func.isRequired,
|
||||
previousPath: PropTypes.string.isRequired,
|
||||
deleteProject: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
SketchList.defaultProps = {
|
||||
|
|
|
@ -30,6 +30,7 @@ import * as ConsoleActions from '../actions/console';
|
|||
import { getHTMLFile } from '../reducers/files';
|
||||
import Overlay from '../../App/components/Overlay';
|
||||
import SketchList from '../components/SketchList';
|
||||
import AssetList from '../components/AssetList';
|
||||
import About from '../components/About';
|
||||
|
||||
class IDEView extends React.Component {
|
||||
|
@ -425,10 +426,30 @@ class IDEView extends React.Component {
|
|||
{(() => { // eslint-disable-line
|
||||
if (this.props.location.pathname.match(/sketches$/)) {
|
||||
return (
|
||||
<Overlay>
|
||||
<Overlay
|
||||
ariaLabel="project list"
|
||||
title="Open a Sketch"
|
||||
previousPath={this.props.ide.previousPath}
|
||||
>
|
||||
<SketchList
|
||||
username={this.props.params.username}
|
||||
previousPath={this.props.ide.previousPath}
|
||||
user={this.props.user}
|
||||
/>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.location.pathname.match(/assets$/)) {
|
||||
return (
|
||||
<Overlay
|
||||
title="Assets"
|
||||
ariaLabel="asset list"
|
||||
previousPath={this.props.ide.previousPath}
|
||||
>
|
||||
<AssetList
|
||||
username={this.props.params.username}
|
||||
user={this.props.user}
|
||||
/>
|
||||
</Overlay>
|
||||
);
|
||||
|
@ -437,7 +458,11 @@ class IDEView extends React.Component {
|
|||
{(() => { // eslint-disable-line
|
||||
if (this.props.location.pathname === '/about') {
|
||||
return (
|
||||
<Overlay>
|
||||
<Overlay
|
||||
previousPath={this.props.ide.previousPath}
|
||||
title="Welcome"
|
||||
ariaLabel="about"
|
||||
>
|
||||
<About previousPath={this.props.ide.previousPath} />
|
||||
</Overlay>
|
||||
);
|
||||
|
@ -446,10 +471,13 @@ class IDEView extends React.Component {
|
|||
{(() => { // eslint-disable-line
|
||||
if (this.props.ide.shareModalVisible) {
|
||||
return (
|
||||
<Overlay>
|
||||
<Overlay
|
||||
title="Share Sketch"
|
||||
ariaLabel="share"
|
||||
closeOverlay={this.props.closeShareModal}
|
||||
>
|
||||
<ShareModal
|
||||
projectId={this.props.project.id}
|
||||
closeShareModal={this.props.closeShareModal}
|
||||
ownerUsername={this.props.project.owner.username}
|
||||
/>
|
||||
</Overlay>
|
||||
|
@ -459,10 +487,12 @@ class IDEView extends React.Component {
|
|||
{(() => { // eslint-disable-line
|
||||
if (this.props.ide.keyboardShortcutVisible) {
|
||||
return (
|
||||
<Overlay>
|
||||
<KeyboardShortcutModal
|
||||
closeModal={this.props.closeKeyboardShortcutModal}
|
||||
/>
|
||||
<Overlay
|
||||
title="Keyboard Shortcuts"
|
||||
ariaLabel="keyboard shortcuts"
|
||||
closeOverlay={this.props.closeKeyboardShortcutModal}
|
||||
>
|
||||
<KeyboardShortcutModal />
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
|
@ -470,10 +500,13 @@ class IDEView extends React.Component {
|
|||
{(() => { // eslint-disable-line
|
||||
if (this.props.ide.errorType) {
|
||||
return (
|
||||
<Overlay>
|
||||
<Overlay
|
||||
title="Error"
|
||||
ariaLabel="error"
|
||||
closeOverlay={this.props.hideErrorModal}
|
||||
>
|
||||
<ErrorModal
|
||||
type={this.props.ide.errorType}
|
||||
closeModal={this.props.hideErrorModal}
|
||||
/>
|
||||
</Overlay>
|
||||
);
|
||||
|
|
12
client/modules/IDE/reducers/assets.js
Normal file
12
client/modules/IDE/reducers/assets.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
const assets = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SET_ASSETS:
|
||||
return action.assets;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default assets;
|
|
@ -9,6 +9,7 @@ import user from './modules/User/reducers';
|
|||
import sketches from './modules/IDE/reducers/projects';
|
||||
import toast from './modules/IDE/reducers/toast';
|
||||
import console from './modules/IDE/reducers/console';
|
||||
import assets from './modules/IDE/reducers/assets';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
form,
|
||||
|
@ -20,7 +21,8 @@ const rootReducer = combineReducers({
|
|||
sketches,
|
||||
editorAccessibility,
|
||||
toast,
|
||||
console
|
||||
console,
|
||||
assets
|
||||
});
|
||||
|
||||
export default rootReducer;
|
||||
|
|
|
@ -49,6 +49,7 @@ const routes = (store) => {
|
|||
<Route path="/sketches" component={IDEView} />
|
||||
<Route path="/:username/sketches/:project_id" component={IDEView} />
|
||||
<Route path="/:username/sketches" component={IDEView} />
|
||||
<Route path="/:username/assets" component={IDEView} />
|
||||
<Route path="/:username/account" component={forceToHttps(AccountView)} />
|
||||
<Route path="/about" component={IDEView} />
|
||||
</Route>
|
||||
|
|
|
@ -186,4 +186,4 @@
|
|||
color: getThemifyVariable('primary-text-color');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,3 @@
|
|||
.about {
|
||||
@extend %modal;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-flow: column;
|
||||
width: #{720 / $base-font-size}rem;
|
||||
outline: none;
|
||||
& a {
|
||||
color: $form-navigation-options-color;
|
||||
}
|
||||
}
|
||||
|
||||
.about__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: #{12 / $base-font-size}rem;
|
||||
padding-right: #{14 / $base-font-size}rem;
|
||||
padding-bottom: #{20 / $base-font-size}rem;
|
||||
padding-left: #{21 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.about__header-title {
|
||||
font-size: #{38 / $base-font-size}rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.about__exit-button {
|
||||
@include icon();
|
||||
}
|
||||
|
||||
.about__logo {
|
||||
@include themify() {
|
||||
& path {
|
||||
|
@ -67,10 +37,15 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
padding-top: #{17 / $base-font-size}rem;
|
||||
padding-right: #{78 / $base-font-size}rem;
|
||||
padding-bottom: #{20 / $base-font-size}rem;
|
||||
padding-left: #{20 / $base-font-size}rem;
|
||||
width: #{720 / $base-font-size}rem;
|
||||
& a {
|
||||
color: $form-navigation-options-color;
|
||||
}
|
||||
}
|
||||
|
||||
.about__content-column {
|
||||
|
@ -110,6 +85,7 @@
|
|||
padding-right: #{20 / $base-font-size}rem;
|
||||
padding-bottom: #{21 / $base-font-size}rem;
|
||||
padding-left: #{291 / $base-font-size}rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.about__footer-list {
|
||||
|
|
56
client/styles/components/_asset-list.scss
Normal file
56
client/styles/components/_asset-list.scss
Normal file
|
@ -0,0 +1,56 @@
|
|||
.asset-table-container {
|
||||
flex: 1 1 0%;
|
||||
overflow-y: scroll;
|
||||
max-width: 100%;
|
||||
width: #{1000 / $base-font-size}rem;
|
||||
min-height: #{400 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.asset-table {
|
||||
width: 100%;
|
||||
padding: #{10 / $base-font-size}rem #{20 / $base-font-size}rem;
|
||||
max-height: 100%;
|
||||
border-spacing: 0;
|
||||
& .asset-list__delete-column {
|
||||
width: #{23 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
& thead {
|
||||
font-size: #{12 / $base-font-size}rem;
|
||||
@include themify() {
|
||||
color: getThemifyVariable('inactive-text-color')
|
||||
}
|
||||
}
|
||||
|
||||
& th {
|
||||
height: #{32 / $base-font-size}rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.asset-table__row {
|
||||
margin: #{10 / $base-font-size}rem;
|
||||
height: #{72 / $base-font-size}rem;
|
||||
font-size: #{16 / $base-font-size}rem;
|
||||
|
||||
&:nth-child(odd) {
|
||||
@include themify() {
|
||||
background: getThemifyVariable('console-header-background-color');
|
||||
}
|
||||
}
|
||||
|
||||
& a {
|
||||
@include themify() {
|
||||
color: getThemifyVariable('primary-text-color');
|
||||
}
|
||||
}
|
||||
|
||||
& td:first-child {
|
||||
padding-left: #{10 / $base-font-size}rem;
|
||||
}
|
||||
}
|
||||
|
||||
.asset-table__empty {
|
||||
text-align: center;
|
||||
font-size: #{16 / $base-font-size}rem;
|
||||
}
|
20
client/styles/components/_keyboard-shortcuts.scss
Normal file
20
client/styles/components/_keyboard-shortcuts.scss
Normal file
|
@ -0,0 +1,20 @@
|
|||
.keyboard-shortcuts {
|
||||
padding: #{20 / $base-font-size}rem;
|
||||
padding-bottom: #{40 / $base-font-size}rem;
|
||||
width: #{450 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.keyboard-shortcut-item {
|
||||
display: flex;
|
||||
& + & {
|
||||
margin-top: #{10 / $base-font-size}rem;
|
||||
}
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.keyboard-shortcut__command {
|
||||
width: 50%;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
padding-right: #{10 / $base-font-size}rem;
|
||||
}
|
|
@ -48,66 +48,3 @@
|
|||
text-align: center;
|
||||
margin: #{20 / $base-font-size}rem 0;
|
||||
}
|
||||
|
||||
.uploader {
|
||||
min-height: #{200 / $base-font-size}rem;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.share-modal {
|
||||
@extend %modal;
|
||||
padding: #{20 / $base-font-size}rem;
|
||||
width: #{500 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.share-modal__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.share-modal__section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: #{10 / $base-font-size}rem 0;
|
||||
}
|
||||
|
||||
.share-modal__label {
|
||||
width: #{86 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.share-modal__input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.keyboard-shortcuts {
|
||||
@extend %modal;
|
||||
padding: #{20 / $base-font-size}rem;
|
||||
width: #{450 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.keyboard-shortcuts__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: #{20 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.keyboard-shortcuts__close {
|
||||
@include icon();
|
||||
}
|
||||
|
||||
.keyboard-shortcut-item {
|
||||
display: flex;
|
||||
& + & {
|
||||
margin-top: #{10 / $base-font-size}rem;
|
||||
}
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.keyboard-shortcut__command {
|
||||
width: 50%;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
padding-right: #{10 / $base-font-size}rem;
|
||||
}
|
||||
|
|
|
@ -9,10 +9,31 @@
|
|||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.overlay-content {
|
||||
.overlay__content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overlay__body {
|
||||
@extend %modal;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-flow: column;
|
||||
max-height: 80%;
|
||||
max-width: 65%;
|
||||
}
|
||||
|
||||
.overlay__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: #{40 / $base-font-size}rem #{20 / $base-font-size}rem #{30 / $base-font-size}rem #{20 / $base-font-size}rem;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.overlay__close-button {
|
||||
@include icon();
|
||||
padding: #{12 / $base-font-size}rem #{16 / $base-font-size}rem;
|
||||
}
|
24
client/styles/components/_share.scss
Normal file
24
client/styles/components/_share.scss
Normal file
|
@ -0,0 +1,24 @@
|
|||
.share-modal {
|
||||
padding: #{20 / $base-font-size}rem;
|
||||
width: #{500 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.share-modal__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.share-modal__section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: #{10 / $base-font-size}rem 0;
|
||||
}
|
||||
|
||||
.share-modal__label {
|
||||
width: #{86 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.share-modal__input {
|
||||
flex: 1;
|
||||
}
|
|
@ -1,25 +1,9 @@
|
|||
.sketch-list {
|
||||
@extend %modal;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-flow: column;
|
||||
width: #{1000 / $base-font-size}rem;
|
||||
height: 80%;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sketch-list__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.sketch-list__header-title {
|
||||
padding: #{40 / $base-font-size}rem #{16 / $base-font-size}rem #{12 / $base-font-size}rem #{21 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.sketches-table-container {
|
||||
flex: 1 0 0%;
|
||||
flex: 1 1 0%;
|
||||
overflow-y: scroll;
|
||||
max-width: 100%;
|
||||
width: #{1000 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.sketches-table {
|
||||
|
@ -67,11 +51,6 @@
|
|||
font-weight: normal;
|
||||
}
|
||||
|
||||
.sketch-list__exit-button {
|
||||
@include icon();
|
||||
margin: #{12 / $base-font-size}rem #{16 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.visibility-toggle .sketch-list__trash-button {
|
||||
@extend %hidden-element;
|
||||
width:#{20 / $base-font-size}rem;
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
.dropzone {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
|
||||
.uploader {
|
||||
min-height: #{200 / $base-font-size}rem;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
|
@ -34,6 +34,9 @@
|
|||
@import 'components/error-modal';
|
||||
@import 'components/preview-frame';
|
||||
@import 'components/help-modal';
|
||||
@import 'components/share';
|
||||
@import 'components/asset-list';
|
||||
@import 'components/keyboard-shortcuts';
|
||||
|
||||
@import 'layout/ide';
|
||||
@import 'layout/fullscreen';
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
"passport": "^0.3.2",
|
||||
"passport-github": "^1.1.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pretty-bytes": "^4.0.2",
|
||||
"project-name-generator": "^2.1.3",
|
||||
"pug": "^2.0.0-beta6",
|
||||
"q": "^1.4.1",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import uuid from 'node-uuid';
|
||||
import policy from 's3-policy';
|
||||
import s3 from 's3';
|
||||
import { getProjectsForUserId } from './project.controller';
|
||||
import { findUserByUsername } from './user.controller';
|
||||
|
||||
const client = s3.createClient({
|
||||
maxAsyncS3: 20,
|
||||
|
@ -104,3 +106,45 @@ export function copyObjectInS3(req, res) {
|
|||
res.json({ url: `${s3Bucket}${userId}/${newFilename}` });
|
||||
});
|
||||
}
|
||||
|
||||
export function listObjectsInS3ForUser(req, res) {
|
||||
const username = req.params.username;
|
||||
findUserByUsername(username, (user) => {
|
||||
const userId = user.id;
|
||||
const params = {
|
||||
s3Params: {
|
||||
Bucket: `${process.env.S3_BUCKET}`,
|
||||
Prefix: `${userId}/`
|
||||
}
|
||||
};
|
||||
let assets = [];
|
||||
const list = client.listObjects(params)
|
||||
.on('data', (data) => {
|
||||
assets = assets.concat(data["Contents"].map((object) => {
|
||||
return { key: object["Key"], size: object["Size"] };
|
||||
}));
|
||||
})
|
||||
.on('end', () => {
|
||||
const projectAssets = [];
|
||||
getProjectsForUserId(userId).then((projects) => {
|
||||
projects.forEach((project) => {
|
||||
project.files.forEach((file) => {
|
||||
if (!file.url) return;
|
||||
|
||||
const foundAsset = assets.find((asset) => file.url.includes(asset.key));
|
||||
if (!foundAsset) return;
|
||||
projectAssets.push({
|
||||
name: file.name,
|
||||
sketchName: project.name,
|
||||
sketchId: project.id,
|
||||
url: file.url,
|
||||
key: foundAsset.key,
|
||||
size: foundAsset.size
|
||||
});
|
||||
});
|
||||
});
|
||||
res.json({assets: projectAssets});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -121,12 +121,28 @@ export function deleteProject(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
export function getProjects(req, res) {
|
||||
if (req.user) {
|
||||
Project.find({ user: req.user._id }) // eslint-disable-line no-underscore-dangle
|
||||
export function getProjectsForUserId(userId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Project.find({ user: userId })
|
||||
.sort('-createdAt')
|
||||
.select('name files id createdAt updatedAt')
|
||||
.exec((err, projects) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
resolve(projects);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function getProjectsForUserName(username) {
|
||||
|
||||
}
|
||||
|
||||
export function getProjects(req, res) {
|
||||
if (req.user) {
|
||||
getProjectsForUserId(req.user._id)
|
||||
.then((projects) => {
|
||||
res.json(projects);
|
||||
});
|
||||
} else {
|
||||
|
@ -142,7 +158,7 @@ export function getProjectsForUser(req, res) {
|
|||
res.status(404).json({ message: 'User with that username does not exist.' });
|
||||
return;
|
||||
}
|
||||
Project.find({ user: user._id }) // eslint-disable-line no-underscore-dangle
|
||||
Project.find({ user: user._id })
|
||||
.sort('-createdAt')
|
||||
.select('name files id createdAt updatedAt')
|
||||
.exec((innerErr, projects) => res.json(projects));
|
||||
|
|
|
@ -15,6 +15,21 @@ const random = (done) => {
|
|||
});
|
||||
};
|
||||
|
||||
export function findUserByUsername(username, cb) {
|
||||
User.findOne({ username },
|
||||
(err, user) => {
|
||||
cb(user);
|
||||
});
|
||||
}
|
||||
|
||||
export function createUser(req, res, next) {
|
||||
const user = new User({
|
||||
username: req.body.username,
|
||||
email: req.body.email,
|
||||
password: req.body.password
|
||||
});
|
||||
};
|
||||
|
||||
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
|
||||
|
||||
export function createUser(req, res, next) {
|
||||
|
|
|
@ -6,5 +6,6 @@ const router = new Router();
|
|||
router.route('/S3/sign').post(AWSController.signS3);
|
||||
router.route('/S3/copy').post(AWSController.copyObjectInS3);
|
||||
router.route('/S3/:object_key').delete(AWSController.deleteObjectFromS3);
|
||||
router.route('/S3/:username/objects').get(AWSController.listObjectsInS3ForUser);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -58,6 +58,12 @@ router.route('/:username/sketches').get((req, res) => {
|
|||
));
|
||||
});
|
||||
|
||||
router.route('/:username/assets').get((req, res) => {
|
||||
userExists(req.params.username, exists => (
|
||||
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))
|
||||
));
|
||||
});
|
||||
|
||||
router.route('/:username/account').get((req, res) => {
|
||||
userExists(req.params.username, exists => (
|
||||
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))
|
||||
|
|
Loading…
Reference in a new issue