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
|
My sketches
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to={`/${this.props.user.username}/assets`}>
|
||||||
|
My assets
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link to={`/${this.props.user.username}/account`}>
|
<Link to={`/${this.props.user.username}/account`}>
|
||||||
My 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 UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
|
||||||
export const TOGGLE_SKETCH = 'TOGGLE_SKETCH';
|
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 SHOW_HELP_MODAL = 'SHOW_HELP_MODAL';
|
||||||
export const HIDE_HELP_MODAL = 'HIDE_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 React, { PropTypes } from 'react';
|
||||||
|
import InlineSVG from 'react-inlinesvg';
|
||||||
|
import { browserHistory } from 'react-router';
|
||||||
|
|
||||||
function Overlay(props) {
|
const exitUrl = require('../../../images/exit.svg');
|
||||||
return (
|
|
||||||
<div className="overlay">
|
class Overlay extends React.Component {
|
||||||
<div className="overlay-content">
|
constructor(props) {
|
||||||
{props.children}
|
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>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Overlay.propTypes = {
|
Overlay.propTypes = {
|
||||||
children: PropTypes.element
|
children: PropTypes.element,
|
||||||
|
closeOverlay: PropTypes.func,
|
||||||
|
title: PropTypes.string,
|
||||||
|
ariaLabel: PropTypes.string,
|
||||||
|
previousPath: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
Overlay.defaultProps = {
|
Overlay.defaultProps = {
|
||||||
children: null
|
children: null,
|
||||||
|
title: 'Modal',
|
||||||
|
closeOverlay: null,
|
||||||
|
ariaLabel: 'modal'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Overlay;
|
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 InlineSVG from 'react-inlinesvg';
|
||||||
import { browserHistory } from 'react-router';
|
|
||||||
|
|
||||||
const exitUrl = require('../../../images/exit.svg');
|
|
||||||
const squareLogoUrl = require('../../../images/p5js-square-logo.svg');
|
const squareLogoUrl = require('../../../images/p5js-square-logo.svg');
|
||||||
const playUrl = require('../../../images/play.svg');
|
const playUrl = require('../../../images/play.svg');
|
||||||
const asteriskUrl = require('../../../images/p5-asterisk.svg');
|
const asteriskUrl = require('../../../images/p5-asterisk.svg');
|
||||||
|
|
||||||
class About extends React.Component {
|
function About(props) {
|
||||||
constructor(props) {
|
return (
|
||||||
super(props);
|
<div className="about__content">
|
||||||
this.closeAboutModal = this.closeAboutModal.bind(this);
|
<div className="about__content-column">
|
||||||
}
|
<InlineSVG className="about__logo" src={squareLogoUrl} alt="p5js Square Logo" />
|
||||||
|
<p className="about__play-video">
|
||||||
componentDidMount() {
|
<a
|
||||||
this.aboutSection.focus();
|
href="http://hello.p5js.org/"
|
||||||
}
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
closeAboutModal() {
|
>
|
||||||
browserHistory.push(this.props.previousPath);
|
<InlineSVG className="about__play-video-button" src={playUrl} alt="Play Hello Video" />
|
||||||
}
|
Play hello! video</a>
|
||||||
|
</p>
|
||||||
render() {
|
</div>
|
||||||
return (
|
<div className="about__content-column">
|
||||||
<section className="about" ref={(element) => { this.aboutSection = element; }} tabIndex="0">
|
<h3 className="about__content-column-title">New to p5.js?</h3>
|
||||||
<header className="about__header">
|
<p className="about__content-column-list">
|
||||||
<h2 className="about__header-title">Welcome</h2>
|
<a
|
||||||
<button className="about__exit-button" onClick={this.closeAboutModal}>
|
href="https://p5js.org/examples/"
|
||||||
<InlineSVG src={exitUrl} alt="Close About Overlay" />
|
target="_blank"
|
||||||
</button>
|
rel="noopener noreferrer"
|
||||||
</header>
|
>
|
||||||
<div className="about__content">
|
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||||
<div className="about__content-column">
|
Examples</a>
|
||||||
<InlineSVG className="about__logo" src={squareLogoUrl} alt="p5js Square Logo" />
|
</p>
|
||||||
<p className="about__play-video">
|
<p className="about__content-column-list">
|
||||||
<a
|
<a
|
||||||
href="http://hello.p5js.org/"
|
href="https://p5js.org/tutorials/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<InlineSVG className="about__play-video-button" src={playUrl} alt="Play Hello Video" />
|
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||||
Play hello! video</a>
|
Tutorials</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="about__content-column">
|
<div className="about__content-column">
|
||||||
<h3 className="about__content-column-title">New to p5.js?</h3>
|
<h3 className="about__content-column-title">Resources</h3>
|
||||||
<p className="about__content-column-list">
|
<p className="about__content-column-list">
|
||||||
<a
|
<a
|
||||||
href="https://p5js.org/examples/"
|
href="https://p5js.org/libraries/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||||
Examples</a>
|
Libraries</a>
|
||||||
</p>
|
</p>
|
||||||
<p className="about__content-column-list">
|
<p className="about__content-column-list">
|
||||||
<a
|
<a
|
||||||
href="https://p5js.org/tutorials/"
|
href="https://p5js.org/reference/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||||
Tutorials</a>
|
Reference</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<p className="about__content-column-list">
|
||||||
<div className="about__content-column">
|
<a
|
||||||
<h3 className="about__content-column-title">Resources</h3>
|
href="https://forum.processing.org/two/"
|
||||||
<p className="about__content-column-list">
|
target="_blank"
|
||||||
<a
|
rel="noopener noreferrer"
|
||||||
href="https://p5js.org/libraries/"
|
>
|
||||||
target="_blank"
|
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||||
rel="noopener noreferrer"
|
Forum</a>
|
||||||
>
|
</p>
|
||||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
</div>
|
||||||
Libraries</a>
|
<div className="about__footer">
|
||||||
</p>
|
<p className="about__footer-list">
|
||||||
<p className="about__content-column-list">
|
<a
|
||||||
<a
|
href="https://github.com/processing/p5.js-web-editor"
|
||||||
href="https://p5js.org/reference/"
|
target="_blank"
|
||||||
target="_blank"
|
rel="noopener noreferrer"
|
||||||
rel="noopener noreferrer"
|
>Contribute</a>
|
||||||
>
|
</p>
|
||||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
<p className="about__footer-list">
|
||||||
Reference</a>
|
<a
|
||||||
</p>
|
href="https://github.com/processing/p5.js-web-editor/issues/new"
|
||||||
<p className="about__content-column-list">
|
target="_blank"
|
||||||
<a
|
rel="noopener noreferrer"
|
||||||
href="https://forum.processing.org/two/"
|
>Report a bug</a>
|
||||||
target="_blank"
|
</p>
|
||||||
rel="noopener noreferrer"
|
<p className="about__footer-list">
|
||||||
>
|
<a
|
||||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
href="https://twitter.com/p5xjs?lang=en"
|
||||||
Forum</a>
|
target="_blank"
|
||||||
</p>
|
rel="noopener noreferrer"
|
||||||
</div>
|
>Twitter</a>
|
||||||
</div>
|
</p>
|
||||||
<div className="about__footer">
|
</div>
|
||||||
<p className="about__footer-list">
|
</div>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
About.propTypes = {
|
|
||||||
previousPath: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default About;
|
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 React, { PropTypes } from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
const exitUrl = require('../../../images/exit.svg');
|
|
||||||
|
|
||||||
class ErrorModal extends React.Component {
|
class ErrorModal extends React.Component {
|
||||||
componentDidMount() {
|
|
||||||
this.errorModal.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
forceAuthentication() {
|
forceAuthentication() {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
|
@ -40,25 +32,17 @@ class ErrorModal extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<section className="error-modal" ref={(element) => { this.errorModal = element; }} tabIndex="0">
|
<div className="error-modal__content">
|
||||||
<header className="error-modal__header">
|
{(() => { // eslint-disable-line
|
||||||
<h2 className="error-modal__title">Error</h2>
|
if (this.props.type === 'forceAuthentication') {
|
||||||
<button className="error-modal__exit-button" onClick={this.props.closeModal}>
|
return this.forceAuthentication();
|
||||||
<InlineSVG src={exitUrl} alt="Close Error Modal" />
|
} else if (this.props.type === 'staleSession') {
|
||||||
</button>
|
return this.staleSession();
|
||||||
</header>
|
} else if (this.props.type === 'staleProject') {
|
||||||
<div className="error-modal__content">
|
return this.staleProject();
|
||||||
{(() => { // eslint-disable-line
|
}
|
||||||
if (this.props.type === 'forceAuthentication') {
|
})()}
|
||||||
return this.forceAuthentication();
|
</div>
|
||||||
} else if (this.props.type === 'staleSession') {
|
|
||||||
return this.staleSession();
|
|
||||||
} else if (this.props.type === 'staleProject') {
|
|
||||||
return this.staleProject();
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,105 +1,84 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
metaKeyName,
|
metaKeyName,
|
||||||
} from '../../../utils/metaKey';
|
} from '../../../utils/metaKey';
|
||||||
|
|
||||||
const exitUrl = require('../../../images/exit.svg');
|
function KeyboardShortcutModal() {
|
||||||
|
return (
|
||||||
class KeyboardShortcutModal extends React.Component {
|
<ul className="keyboard-shortcuts" title="keyboard shortcuts">
|
||||||
componentDidMount() {
|
<li className="keyboard-shortcut-item">
|
||||||
this.isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1;
|
<span className="keyboard-shortcut__command">Shift + Tab</span>
|
||||||
}
|
<span>Tidy</span>
|
||||||
|
</li>
|
||||||
render() {
|
<li className="keyboard-shortcut-item">
|
||||||
return (
|
<span className="keyboard-shortcut__command">
|
||||||
<section className="keyboard-shortcuts">
|
{metaKeyName} + S
|
||||||
<header className="keyboard-shortcuts__header">
|
</span>
|
||||||
<h2>Keyboard Shortcuts</h2>
|
<span>Save</span>
|
||||||
<button className="keyboard-shortcuts__close" onClick={this.props.closeModal}>
|
</li>
|
||||||
<InlineSVG src={exitUrl} alt="Close Keyboard Shortcuts Overlay" />
|
<li className="keyboard-shortcut-item">
|
||||||
</button>
|
<span className="keyboard-shortcut__command">
|
||||||
</header>
|
{metaKeyName} + F
|
||||||
<ul title="keyboard shortcuts">
|
</span>
|
||||||
<li className="keyboard-shortcut-item">
|
<span>Find Text</span>
|
||||||
<span className="keyboard-shortcut__command">Shift + Tab</span>
|
</li>
|
||||||
<span>Tidy</span>
|
<li className="keyboard-shortcut-item">
|
||||||
</li>
|
<span className="keyboard-shortcut__command">
|
||||||
<li className="keyboard-shortcut-item">
|
{metaKeyName} + G
|
||||||
<span className="keyboard-shortcut__command">
|
</span>
|
||||||
{metaKeyName} + S
|
<span>Find Next Text Match</span>
|
||||||
</span>
|
</li>
|
||||||
<span>Save</span>
|
<li className="keyboard-shortcut-item">
|
||||||
</li>
|
<span className="keyboard-shortcut__command">
|
||||||
<li className="keyboard-shortcut-item">
|
{metaKeyName} + Shift + G
|
||||||
<span className="keyboard-shortcut__command">
|
</span>
|
||||||
{metaKeyName} + F
|
<span>Find Previous Text Match</span>
|
||||||
</span>
|
</li>
|
||||||
<span>Find Text</span>
|
<li className="keyboard-shortcut-item">
|
||||||
</li>
|
<span className="keyboard-shortcut__command">
|
||||||
<li className="keyboard-shortcut-item">
|
{metaKeyName} + [
|
||||||
<span className="keyboard-shortcut__command">
|
</span>
|
||||||
{metaKeyName} + G
|
<span>Indent Code Left</span>
|
||||||
</span>
|
</li>
|
||||||
<span>Find Next Text Match</span>
|
<li className="keyboard-shortcut-item">
|
||||||
</li>
|
<span className="keyboard-shortcut__command">
|
||||||
<li className="keyboard-shortcut-item">
|
{metaKeyName} + ]
|
||||||
<span className="keyboard-shortcut__command">
|
</span>
|
||||||
{metaKeyName} + Shift + G
|
<span>Indent Code Right</span>
|
||||||
</span>
|
</li>
|
||||||
<span>Find Previous Text Match</span>
|
<li className="keyboard-shortcut-item">
|
||||||
</li>
|
<span className="keyboard-shortcut__command">
|
||||||
<li className="keyboard-shortcut-item">
|
{metaKeyName} + /
|
||||||
<span className="keyboard-shortcut__command">
|
</span>
|
||||||
{metaKeyName} + [
|
<span>Comment Line</span>
|
||||||
</span>
|
</li>
|
||||||
<span>Indent Code Left</span>
|
<li className="keyboard-shortcut-item">
|
||||||
</li>
|
<span className="keyboard-shortcut__command">
|
||||||
<li className="keyboard-shortcut-item">
|
{metaKeyName} + Enter
|
||||||
<span className="keyboard-shortcut__command">
|
</span>
|
||||||
{metaKeyName} + ]
|
<span>Start Sketch</span>
|
||||||
</span>
|
</li>
|
||||||
<span>Indent Code Right</span>
|
<li className="keyboard-shortcut-item">
|
||||||
</li>
|
<span className="keyboard-shortcut__command">
|
||||||
<li className="keyboard-shortcut-item">
|
{metaKeyName} + Shift + Enter
|
||||||
<span className="keyboard-shortcut__command">
|
</span>
|
||||||
{metaKeyName} + /
|
<span>Stop Sketch</span>
|
||||||
</span>
|
</li>
|
||||||
<span>Comment Line</span>
|
<li className="keyboard-shortcut-item">
|
||||||
</li>
|
<span className="keyboard-shortcut__command">
|
||||||
<li className="keyboard-shortcut-item">
|
{metaKeyName} + Shift + 1
|
||||||
<span className="keyboard-shortcut__command">
|
</span>
|
||||||
{metaKeyName} + Enter
|
<span>Toggle Text-based Canvas</span>
|
||||||
</span>
|
</li>
|
||||||
<span>Start Sketch</span>
|
<li className="keyboard-shortcut-item">
|
||||||
</li>
|
<span className="keyboard-shortcut__command">
|
||||||
<li className="keyboard-shortcut-item">
|
{metaKeyName} + Shift + 2
|
||||||
<span className="keyboard-shortcut__command">
|
</span>
|
||||||
{metaKeyName} + Shift + Enter
|
<span>Turn Off Text-based Canvas</span>
|
||||||
</span>
|
</li>
|
||||||
<span>Stop Sketch</span>
|
</ul>
|
||||||
</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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardShortcutModal.propTypes = {
|
|
||||||
closeModal: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KeyboardShortcutModal;
|
export default KeyboardShortcutModal;
|
||||||
|
|
|
@ -1,57 +1,46 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
|
|
||||||
const exitUrl = require('../../../images/exit.svg');
|
function ShareModal(props) {
|
||||||
|
const {
|
||||||
class ShareModal extends React.Component {
|
projectId,
|
||||||
componentDidMount() {
|
ownerUsername
|
||||||
this.shareModal.focus();
|
} = props;
|
||||||
}
|
const hostname = window.location.origin;
|
||||||
render() {
|
return (
|
||||||
const hostname = window.location.origin;
|
<div className="share-modal">
|
||||||
return (
|
<div className="share-modal__section">
|
||||||
<section className="share-modal" ref={(element) => { this.shareModal = element; }} tabIndex="0">
|
<label className="share-modal__label" htmlFor="share-modal__embed">Embed</label>
|
||||||
<header className="share-modal__header">
|
<input
|
||||||
<h2>Share Sketch</h2>
|
type="text"
|
||||||
<button className="about__exit-button" onClick={this.props.closeShareModal}>
|
className="share-modal__input"
|
||||||
<InlineSVG src={exitUrl} alt="Close Share Overlay" />
|
id="share-modal__embed"
|
||||||
</button>
|
value={`<iframe src="${hostname}/embed/${projectId}"></iframe>`}
|
||||||
</header>
|
/>
|
||||||
<div className="share-modal__section">
|
</div>
|
||||||
<label className="share-modal__label" htmlFor="share-modal__embed">Embed</label>
|
<div className="share-modal__section">
|
||||||
<input
|
<label className="share-modal__label" htmlFor="share-modal__fullscreen">Fullscreen</label>
|
||||||
type="text"
|
<input
|
||||||
className="share-modal__input"
|
type="text"
|
||||||
id="share-modal__embed"
|
className="share-modal__input"
|
||||||
value={`<iframe src="${hostname}/embed/${this.props.projectId}"></iframe>`}
|
id="share-modal__fullscreen"
|
||||||
/>
|
value={`${hostname}/full/${projectId}`}
|
||||||
</div>
|
/>
|
||||||
<div className="share-modal__section">
|
</div>
|
||||||
<label className="share-modal__label" htmlFor="share-modal__fullscreen">Fullscreen</label>
|
<div className="share-modal__section">
|
||||||
<input
|
<label className="share-modal__label" htmlFor="share-modal__edit">Edit</label>
|
||||||
type="text"
|
<input
|
||||||
className="share-modal__input"
|
type="text"
|
||||||
id="share-modal__fullscreen"
|
className="share-modal__input"
|
||||||
value={`${hostname}/full/${this.props.projectId}`}
|
id="share-modal__edit"
|
||||||
/>
|
value={`${hostname}/${ownerUsername}/sketches/${projectId}`}
|
||||||
</div>
|
/>
|
||||||
<div className="share-modal__section">
|
</div>
|
||||||
<label className="share-modal__label" htmlFor="share-modal__edit">Edit</label>
|
</div>
|
||||||
<input
|
);
|
||||||
type="text"
|
|
||||||
className="share-modal__input"
|
|
||||||
id="share-modal__edit"
|
|
||||||
value={`${hostname}/${this.props.ownerUsername}/sketches/${this.props.projectId}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ShareModal.propTypes = {
|
ShareModal.propTypes = {
|
||||||
projectId: PropTypes.string.isRequired,
|
projectId: PropTypes.string.isRequired,
|
||||||
closeShareModal: PropTypes.func.isRequired,
|
|
||||||
ownerUsername: PropTypes.string.isRequired
|
ownerUsername: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,80 +8,62 @@ import * as SketchActions from '../actions/projects';
|
||||||
import * as ProjectActions from '../actions/project';
|
import * as ProjectActions from '../actions/project';
|
||||||
import * as ToastActions from '../actions/toast';
|
import * as ToastActions from '../actions/toast';
|
||||||
|
|
||||||
const exitUrl = require('../../../images/exit.svg');
|
|
||||||
const trashCan = require('../../../images/trash-can.svg');
|
const trashCan = require('../../../images/trash-can.svg');
|
||||||
|
|
||||||
class SketchList extends React.Component {
|
class SketchList extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.closeSketchList = this.closeSketchList.bind(this);
|
|
||||||
this.props.getProjects(this.props.username);
|
this.props.getProjects(this.props.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
document.getElementById('sketchlist').focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
closeSketchList() {
|
|
||||||
browserHistory.push(this.props.previousPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
|
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
|
||||||
return (
|
return (
|
||||||
<section className="sketch-list" aria-label="project list" tabIndex="0" role="main" id="sketchlist">
|
<div className="sketches-table-container">
|
||||||
<header className="sketch-list__header">
|
<table className="sketches-table" summary="table containing all saved projects">
|
||||||
<h2 className="sketch-list__header-title">Open a Sketch</h2>
|
<thead>
|
||||||
<button className="sketch-list__exit-button" onClick={this.closeSketchList}>
|
<tr>
|
||||||
<InlineSVG src={exitUrl} alt="Close Sketch List Overlay" />
|
<th className="sketch-list__trash-column" scope="col"></th>
|
||||||
</button>
|
<th scope="col">Sketch</th>
|
||||||
</header>
|
<th scope="col">Date created</th>
|
||||||
<div className="sketches-table-container">
|
<th scope="col">Date updated</th>
|
||||||
<table className="sketches-table" summary="table containing all saved projects">
|
</tr>
|
||||||
<thead>
|
</thead>
|
||||||
<tr>
|
<tbody>
|
||||||
<th className="sketch-list__trash-column" scope="col"></th>
|
{this.props.sketches.map(sketch =>
|
||||||
<th scope="col">Sketch</th>
|
// eslint-disable-next-line
|
||||||
<th scope="col">Date created</th>
|
<tr
|
||||||
<th scope="col">Date updated</th>
|
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>
|
</tr>
|
||||||
</thead>
|
)}
|
||||||
<tbody>
|
</tbody>
|
||||||
{this.props.sketches.map(sketch =>
|
</table>
|
||||||
// eslint-disable-next-line
|
</div>
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,8 +80,7 @@ SketchList.propTypes = {
|
||||||
updatedAt: PropTypes.string.isRequired
|
updatedAt: PropTypes.string.isRequired
|
||||||
})).isRequired,
|
})).isRequired,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
deleteProject: PropTypes.func.isRequired,
|
deleteProject: PropTypes.func.isRequired
|
||||||
previousPath: PropTypes.string.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SketchList.defaultProps = {
|
SketchList.defaultProps = {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import * as ConsoleActions from '../actions/console';
|
||||||
import { getHTMLFile } from '../reducers/files';
|
import { getHTMLFile } from '../reducers/files';
|
||||||
import Overlay from '../../App/components/Overlay';
|
import Overlay from '../../App/components/Overlay';
|
||||||
import SketchList from '../components/SketchList';
|
import SketchList from '../components/SketchList';
|
||||||
|
import AssetList from '../components/AssetList';
|
||||||
import About from '../components/About';
|
import About from '../components/About';
|
||||||
|
|
||||||
class IDEView extends React.Component {
|
class IDEView extends React.Component {
|
||||||
|
@ -425,10 +426,30 @@ class IDEView extends React.Component {
|
||||||
{(() => { // eslint-disable-line
|
{(() => { // eslint-disable-line
|
||||||
if (this.props.location.pathname.match(/sketches$/)) {
|
if (this.props.location.pathname.match(/sketches$/)) {
|
||||||
return (
|
return (
|
||||||
<Overlay>
|
<Overlay
|
||||||
|
ariaLabel="project list"
|
||||||
|
title="Open a Sketch"
|
||||||
|
previousPath={this.props.ide.previousPath}
|
||||||
|
>
|
||||||
<SketchList
|
<SketchList
|
||||||
username={this.props.params.username}
|
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>
|
</Overlay>
|
||||||
);
|
);
|
||||||
|
@ -437,7 +458,11 @@ class IDEView extends React.Component {
|
||||||
{(() => { // eslint-disable-line
|
{(() => { // eslint-disable-line
|
||||||
if (this.props.location.pathname === '/about') {
|
if (this.props.location.pathname === '/about') {
|
||||||
return (
|
return (
|
||||||
<Overlay>
|
<Overlay
|
||||||
|
previousPath={this.props.ide.previousPath}
|
||||||
|
title="Welcome"
|
||||||
|
ariaLabel="about"
|
||||||
|
>
|
||||||
<About previousPath={this.props.ide.previousPath} />
|
<About previousPath={this.props.ide.previousPath} />
|
||||||
</Overlay>
|
</Overlay>
|
||||||
);
|
);
|
||||||
|
@ -446,10 +471,13 @@ class IDEView extends React.Component {
|
||||||
{(() => { // eslint-disable-line
|
{(() => { // eslint-disable-line
|
||||||
if (this.props.ide.shareModalVisible) {
|
if (this.props.ide.shareModalVisible) {
|
||||||
return (
|
return (
|
||||||
<Overlay>
|
<Overlay
|
||||||
|
title="Share Sketch"
|
||||||
|
ariaLabel="share"
|
||||||
|
closeOverlay={this.props.closeShareModal}
|
||||||
|
>
|
||||||
<ShareModal
|
<ShareModal
|
||||||
projectId={this.props.project.id}
|
projectId={this.props.project.id}
|
||||||
closeShareModal={this.props.closeShareModal}
|
|
||||||
ownerUsername={this.props.project.owner.username}
|
ownerUsername={this.props.project.owner.username}
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
|
@ -459,10 +487,12 @@ class IDEView extends React.Component {
|
||||||
{(() => { // eslint-disable-line
|
{(() => { // eslint-disable-line
|
||||||
if (this.props.ide.keyboardShortcutVisible) {
|
if (this.props.ide.keyboardShortcutVisible) {
|
||||||
return (
|
return (
|
||||||
<Overlay>
|
<Overlay
|
||||||
<KeyboardShortcutModal
|
title="Keyboard Shortcuts"
|
||||||
closeModal={this.props.closeKeyboardShortcutModal}
|
ariaLabel="keyboard shortcuts"
|
||||||
/>
|
closeOverlay={this.props.closeKeyboardShortcutModal}
|
||||||
|
>
|
||||||
|
<KeyboardShortcutModal />
|
||||||
</Overlay>
|
</Overlay>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -470,10 +500,13 @@ class IDEView extends React.Component {
|
||||||
{(() => { // eslint-disable-line
|
{(() => { // eslint-disable-line
|
||||||
if (this.props.ide.errorType) {
|
if (this.props.ide.errorType) {
|
||||||
return (
|
return (
|
||||||
<Overlay>
|
<Overlay
|
||||||
|
title="Error"
|
||||||
|
ariaLabel="error"
|
||||||
|
closeOverlay={this.props.hideErrorModal}
|
||||||
|
>
|
||||||
<ErrorModal
|
<ErrorModal
|
||||||
type={this.props.ide.errorType}
|
type={this.props.ide.errorType}
|
||||||
closeModal={this.props.hideErrorModal}
|
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</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 sketches from './modules/IDE/reducers/projects';
|
||||||
import toast from './modules/IDE/reducers/toast';
|
import toast from './modules/IDE/reducers/toast';
|
||||||
import console from './modules/IDE/reducers/console';
|
import console from './modules/IDE/reducers/console';
|
||||||
|
import assets from './modules/IDE/reducers/assets';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
form,
|
form,
|
||||||
|
@ -20,7 +21,8 @@ const rootReducer = combineReducers({
|
||||||
sketches,
|
sketches,
|
||||||
editorAccessibility,
|
editorAccessibility,
|
||||||
toast,
|
toast,
|
||||||
console
|
console,
|
||||||
|
assets
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -49,6 +49,7 @@ const routes = (store) => {
|
||||||
<Route path="/sketches" component={IDEView} />
|
<Route path="/sketches" component={IDEView} />
|
||||||
<Route path="/:username/sketches/:project_id" component={IDEView} />
|
<Route path="/:username/sketches/:project_id" component={IDEView} />
|
||||||
<Route path="/:username/sketches" component={IDEView} />
|
<Route path="/:username/sketches" component={IDEView} />
|
||||||
|
<Route path="/:username/assets" component={IDEView} />
|
||||||
<Route path="/:username/account" component={forceToHttps(AccountView)} />
|
<Route path="/:username/account" component={forceToHttps(AccountView)} />
|
||||||
<Route path="/about" component={IDEView} />
|
<Route path="/about" component={IDEView} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
@ -186,4 +186,4 @@
|
||||||
color: getThemifyVariable('primary-text-color');
|
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 {
|
.about__logo {
|
||||||
@include themify() {
|
@include themify() {
|
||||||
& path {
|
& path {
|
||||||
|
@ -67,10 +37,15 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
padding-top: #{17 / $base-font-size}rem;
|
padding-top: #{17 / $base-font-size}rem;
|
||||||
padding-right: #{78 / $base-font-size}rem;
|
padding-right: #{78 / $base-font-size}rem;
|
||||||
padding-bottom: #{20 / $base-font-size}rem;
|
padding-bottom: #{20 / $base-font-size}rem;
|
||||||
padding-left: #{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 {
|
.about__content-column {
|
||||||
|
@ -110,6 +85,7 @@
|
||||||
padding-right: #{20 / $base-font-size}rem;
|
padding-right: #{20 / $base-font-size}rem;
|
||||||
padding-bottom: #{21 / $base-font-size}rem;
|
padding-bottom: #{21 / $base-font-size}rem;
|
||||||
padding-left: #{291 / $base-font-size}rem;
|
padding-left: #{291 / $base-font-size}rem;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about__footer-list {
|
.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;
|
text-align: center;
|
||||||
margin: #{20 / $base-font-size}rem 0;
|
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;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-content {
|
.overlay__content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: 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 {
|
.sketches-table-container {
|
||||||
flex: 1 0 0%;
|
flex: 1 1 0%;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
max-width: 100%;
|
||||||
|
width: #{1000 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sketches-table {
|
.sketches-table {
|
||||||
|
@ -67,11 +51,6 @@
|
||||||
font-weight: normal;
|
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 {
|
.visibility-toggle .sketch-list__trash-button {
|
||||||
@extend %hidden-element;
|
@extend %hidden-element;
|
||||||
width:#{20 / $base-font-size}rem;
|
width:#{20 / $base-font-size}rem;
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
.dropzone {
|
.dropzone {
|
||||||
color: $primary-text-color;
|
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/error-modal';
|
||||||
@import 'components/preview-frame';
|
@import 'components/preview-frame';
|
||||||
@import 'components/help-modal';
|
@import 'components/help-modal';
|
||||||
|
@import 'components/share';
|
||||||
|
@import 'components/asset-list';
|
||||||
|
@import 'components/keyboard-shortcuts';
|
||||||
|
|
||||||
@import 'layout/ide';
|
@import 'layout/ide';
|
||||||
@import 'layout/fullscreen';
|
@import 'layout/fullscreen';
|
||||||
|
|
|
@ -97,6 +97,7 @@
|
||||||
"passport": "^0.3.2",
|
"passport": "^0.3.2",
|
||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"pretty-bytes": "^4.0.2",
|
||||||
"project-name-generator": "^2.1.3",
|
"project-name-generator": "^2.1.3",
|
||||||
"pug": "^2.0.0-beta6",
|
"pug": "^2.0.0-beta6",
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import uuid from 'node-uuid';
|
import uuid from 'node-uuid';
|
||||||
import policy from 's3-policy';
|
import policy from 's3-policy';
|
||||||
import s3 from 's3';
|
import s3 from 's3';
|
||||||
|
import { getProjectsForUserId } from './project.controller';
|
||||||
|
import { findUserByUsername } from './user.controller';
|
||||||
|
|
||||||
const client = s3.createClient({
|
const client = s3.createClient({
|
||||||
maxAsyncS3: 20,
|
maxAsyncS3: 20,
|
||||||
|
@ -104,3 +106,45 @@ export function copyObjectInS3(req, res) {
|
||||||
res.json({ url: `${s3Bucket}${userId}/${newFilename}` });
|
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) {
|
export function getProjectsForUserId(userId) {
|
||||||
if (req.user) {
|
return new Promise((resolve, reject) => {
|
||||||
Project.find({ user: req.user._id }) // eslint-disable-line no-underscore-dangle
|
Project.find({ user: userId })
|
||||||
.sort('-createdAt')
|
.sort('-createdAt')
|
||||||
.select('name files id createdAt updatedAt')
|
.select('name files id createdAt updatedAt')
|
||||||
.exec((err, projects) => {
|
.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);
|
res.json(projects);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -142,7 +158,7 @@ export function getProjectsForUser(req, res) {
|
||||||
res.status(404).json({ message: 'User with that username does not exist.' });
|
res.status(404).json({ message: 'User with that username does not exist.' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Project.find({ user: user._id }) // eslint-disable-line no-underscore-dangle
|
Project.find({ user: user._id })
|
||||||
.sort('-createdAt')
|
.sort('-createdAt')
|
||||||
.select('name files id createdAt updatedAt')
|
.select('name files id createdAt updatedAt')
|
||||||
.exec((innerErr, projects) => res.json(projects));
|
.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
|
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
|
||||||
|
|
||||||
export function createUser(req, res, next) {
|
export function createUser(req, res, next) {
|
||||||
|
|
|
@ -6,5 +6,6 @@ const router = new Router();
|
||||||
router.route('/S3/sign').post(AWSController.signS3);
|
router.route('/S3/sign').post(AWSController.signS3);
|
||||||
router.route('/S3/copy').post(AWSController.copyObjectInS3);
|
router.route('/S3/copy').post(AWSController.copyObjectInS3);
|
||||||
router.route('/S3/:object_key').delete(AWSController.deleteObjectFromS3);
|
router.route('/S3/:object_key').delete(AWSController.deleteObjectFromS3);
|
||||||
|
router.route('/S3/:username/objects').get(AWSController.listObjectsInS3ForUser);
|
||||||
|
|
||||||
export default router;
|
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) => {
|
router.route('/:username/account').get((req, res) => {
|
||||||
userExists(req.params.username, exists => (
|
userExists(req.params.username, exists => (
|
||||||
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))
|
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))
|
||||||
|
|
Loading…
Reference in a new issue