-
- {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 (
+
+
+ { this.overlay = element; }}
+ className="overlay__body"
+ >
+
+ {children}
+
+
-
- );
+ );
+ }
}
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;
diff --git a/client/modules/IDE/actions/assets.js b/client/modules/IDE/actions/assets.js
new file mode 100644
index 00000000..8cceb91e
--- /dev/null
+++ b/client/modules/IDE/actions/assets.js
@@ -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'
+ };
+}
diff --git a/client/modules/IDE/components/About.jsx b/client/modules/IDE/components/About.jsx
index 7fa2bd39..aa734c7c 100644
--- a/client/modules/IDE/components/About.jsx
+++ b/client/modules/IDE/components/About.jsx
@@ -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 (
-
{ this.aboutSection = element; }} tabIndex="0">
-
-
-
-
- );
- }
+function About(props) {
+ return (
+
+ );
}
-About.propTypes = {
- previousPath: PropTypes.string.isRequired
-};
-
export default About;
diff --git a/client/modules/IDE/components/AssetList.jsx b/client/modules/IDE/components/AssetList.jsx
new file mode 100644
index 00000000..e78de28c
--- /dev/null
+++ b/client/modules/IDE/components/AssetList.jsx
@@ -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 (
+
+ {this.props.assets.length === 0 &&
+
No uploaded assets.
+ }
+ {this.props.assets.length > 0 &&
+
+
+
+ Name |
+ Size |
+ View |
+ Sketch |
+
+
+
+ {this.props.assets.map(asset =>
+
+ {asset.name} |
+ {prettyBytes(asset.size)} |
+ View |
+ {asset.sketchName} |
+
+ )}
+
+
}
+
+ );
+ }
+}
+
+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);
diff --git a/client/modules/IDE/components/ErrorModal.jsx b/client/modules/IDE/components/ErrorModal.jsx
index bf62721a..b7fd0851 100644
--- a/client/modules/IDE/components/ErrorModal.jsx
+++ b/client/modules/IDE/components/ErrorModal.jsx
@@ -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 (
@@ -40,25 +32,17 @@ class ErrorModal extends React.Component {
render() {
return (
- { this.errorModal = element; }} tabIndex="0">
-
-
- {(() => { // 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();
- }
- })()}
-
-
+
+ {(() => { // 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();
+ }
+ })()}
+
);
}
}
diff --git a/client/modules/IDE/components/KeyboardShortcutModal.jsx b/client/modules/IDE/components/KeyboardShortcutModal.jsx
index 4101a6f4..0eb399ca 100644
--- a/client/modules/IDE/components/KeyboardShortcutModal.jsx
+++ b/client/modules/IDE/components/KeyboardShortcutModal.jsx
@@ -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 (
-
-
- Keyboard Shortcuts
-
-
-
- -
- Shift + Tab
- Tidy
-
- -
-
- {metaKeyName} + S
-
- Save
-
- -
-
- {metaKeyName} + F
-
- Find Text
-
- -
-
- {metaKeyName} + G
-
- Find Next Text Match
-
- -
-
- {metaKeyName} + Shift + G
-
- Find Previous Text Match
-
- -
-
- {metaKeyName} + [
-
- Indent Code Left
-
- -
-
- {metaKeyName} + ]
-
- Indent Code Right
-
- -
-
- {metaKeyName} + /
-
- Comment Line
-
- -
-
- {metaKeyName} + Enter
-
- Start Sketch
-
- -
-
- {metaKeyName} + Shift + Enter
-
- Stop Sketch
-
- -
-
- {metaKeyName} + Shift + 1
-
- Toggle Text-based Canvas
-
- -
-
- {metaKeyName} + Shift + 2
-
- Turn Off Text-based Canvas
-
-
-
- );
- }
+function KeyboardShortcutModal() {
+ return (
+
+ -
+ Shift + Tab
+ Tidy
+
+ -
+
+ {metaKeyName} + S
+
+ Save
+
+ -
+
+ {metaKeyName} + F
+
+ Find Text
+
+ -
+
+ {metaKeyName} + G
+
+ Find Next Text Match
+
+ -
+
+ {metaKeyName} + Shift + G
+
+ Find Previous Text Match
+
+ -
+
+ {metaKeyName} + [
+
+ Indent Code Left
+
+ -
+
+ {metaKeyName} + ]
+
+ Indent Code Right
+
+ -
+
+ {metaKeyName} + /
+
+ Comment Line
+
+ -
+
+ {metaKeyName} + Enter
+
+ Start Sketch
+
+ -
+
+ {metaKeyName} + Shift + Enter
+
+ Stop Sketch
+
+ -
+
+ {metaKeyName} + Shift + 1
+
+ Toggle Text-based Canvas
+
+ -
+
+ {metaKeyName} + Shift + 2
+
+ Turn Off Text-based Canvas
+
+
+ );
}
-KeyboardShortcutModal.propTypes = {
- closeModal: PropTypes.func.isRequired
-};
-
export default KeyboardShortcutModal;
diff --git a/client/modules/IDE/components/ShareModal.jsx b/client/modules/IDE/components/ShareModal.jsx
index ad8c030e..3b398e1b 100644
--- a/client/modules/IDE/components/ShareModal.jsx
+++ b/client/modules/IDE/components/ShareModal.jsx
@@ -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 (
-
- );
- }
+function ShareModal(props) {
+ const {
+ projectId,
+ ownerUsername
+ } = props;
+ const hostname = window.location.origin;
+ return (
+
+ );
}
ShareModal.propTypes = {
projectId: PropTypes.string.isRequired,
- closeShareModal: PropTypes.func.isRequired,
ownerUsername: PropTypes.string.isRequired
};
diff --git a/client/modules/IDE/components/SketchList.jsx b/client/modules/IDE/components/SketchList.jsx
index a60a1add..7094d72b 100644
--- a/client/modules/IDE/components/SketchList.jsx
+++ b/client/modules/IDE/components/SketchList.jsx
@@ -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 (
-
-
-
-
-
-
- |
- Sketch |
- Date created |
- Date updated |
+
+
+
+
+ |
+ Sketch |
+ Date created |
+ Date updated |
+
+
+
+ {this.props.sketches.map(sketch =>
+ // eslint-disable-next-line
+ browserHistory.push(`/${username}/sketches/${sketch.id}`)}
+ >
+
+ {(() => { // eslint-disable-line
+ if (this.props.username === this.props.user.username || this.props.username === undefined) {
+ return (
+
+ );
+ }
+ })()}
+ |
+ {sketch.name} |
+ {moment(sketch.createdAt).format('MMM D, YYYY h:mm A')} |
+ {moment(sketch.updatedAt).format('MMM D, YYYY h:mm A')} |
-
-
- {this.props.sketches.map(sketch =>
- // eslint-disable-next-line
- browserHistory.push(`/${username}/sketches/${sketch.id}`)}
- >
-
- {(() => { // eslint-disable-line
- if (this.props.username === this.props.user.username || this.props.username === undefined) {
- return (
-
- );
- }
- })()}
- |
- {sketch.name} |
- {moment(sketch.createdAt).format('MMM D, YYYY h:mm A')} |
- {moment(sketch.updatedAt).format('MMM D, YYYY h:mm A')} |
-
- )}
-
-
-
-
+ )}
+
+
+
);
}
}
@@ -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 = {
diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx
index 1968c341..1d503291 100644
--- a/client/modules/IDE/pages/IDEView.jsx
+++ b/client/modules/IDE/pages/IDEView.jsx
@@ -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 (
-
+
+
+ );
+ }
+ })()}
+ {(() => { // eslint-disable-line
+ if (this.props.location.pathname.match(/assets$/)) {
+ return (
+
+
);
@@ -437,7 +458,11 @@ class IDEView extends React.Component {
{(() => { // eslint-disable-line
if (this.props.location.pathname === '/about') {
return (
-
+
);
@@ -446,10 +471,13 @@ class IDEView extends React.Component {
{(() => { // eslint-disable-line
if (this.props.ide.shareModalVisible) {
return (
-
+
@@ -459,10 +487,12 @@ class IDEView extends React.Component {
{(() => { // eslint-disable-line
if (this.props.ide.keyboardShortcutVisible) {
return (
-
-
+
+
);
}
@@ -470,10 +500,13 @@ class IDEView extends React.Component {
{(() => { // eslint-disable-line
if (this.props.ide.errorType) {
return (
-
+
);
diff --git a/client/modules/IDE/reducers/assets.js b/client/modules/IDE/reducers/assets.js
new file mode 100644
index 00000000..260660f2
--- /dev/null
+++ b/client/modules/IDE/reducers/assets.js
@@ -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;
diff --git a/client/reducers.js b/client/reducers.js
index 4505623a..c508c8ea 100644
--- a/client/reducers.js
+++ b/client/reducers.js
@@ -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;
diff --git a/client/routes.jsx b/client/routes.jsx
index f5f7ce6b..556d9970 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -49,6 +49,7 @@ const routes = (store) => {
+
diff --git a/client/styles/abstracts/_placeholders.scss b/client/styles/abstracts/_placeholders.scss
index ec837235..9f3a2038 100644
--- a/client/styles/abstracts/_placeholders.scss
+++ b/client/styles/abstracts/_placeholders.scss
@@ -186,4 +186,4 @@
color: getThemifyVariable('primary-text-color');
}
}
-}
\ No newline at end of file
+}
diff --git a/client/styles/components/_about.scss b/client/styles/components/_about.scss
index 1ca80ea2..25e916ea 100644
--- a/client/styles/components/_about.scss
+++ b/client/styles/components/_about.scss
@@ -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 {
diff --git a/client/styles/components/_asset-list.scss b/client/styles/components/_asset-list.scss
new file mode 100644
index 00000000..5b716170
--- /dev/null
+++ b/client/styles/components/_asset-list.scss
@@ -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;
+}
diff --git a/client/styles/components/_keyboard-shortcuts.scss b/client/styles/components/_keyboard-shortcuts.scss
new file mode 100644
index 00000000..66963e91
--- /dev/null
+++ b/client/styles/components/_keyboard-shortcuts.scss
@@ -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;
+}
diff --git a/client/styles/components/_modal.scss b/client/styles/components/_modal.scss
index 470509af..06830684 100644
--- a/client/styles/components/_modal.scss
+++ b/client/styles/components/_modal.scss
@@ -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;
-}
diff --git a/client/styles/components/_overlay.scss b/client/styles/components/_overlay.scss
index 9d952969..690d1c7e 100644
--- a/client/styles/components/_overlay.scss
+++ b/client/styles/components/_overlay.scss
@@ -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;
}
\ No newline at end of file
diff --git a/client/styles/components/_share.scss b/client/styles/components/_share.scss
new file mode 100644
index 00000000..a9b72c7e
--- /dev/null
+++ b/client/styles/components/_share.scss
@@ -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;
+}
\ No newline at end of file
diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss
index 9775b04c..034ab59b 100644
--- a/client/styles/components/_sketch-list.scss
+++ b/client/styles/components/_sketch-list.scss
@@ -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;
diff --git a/client/styles/components/_uploader.scss b/client/styles/components/_uploader.scss
index e6d8bcee..695e84da 100644
--- a/client/styles/components/_uploader.scss
+++ b/client/styles/components/_uploader.scss
@@ -1,3 +1,9 @@
.dropzone {
color: $primary-text-color;
+}
+
+.uploader {
+ min-height: #{200 / $base-font-size}rem;
+ width: 100%;
+ text-align: center;
}
\ No newline at end of file
diff --git a/client/styles/main.scss b/client/styles/main.scss
index fe97261e..abe5a93b 100644
--- a/client/styles/main.scss
+++ b/client/styles/main.scss
@@ -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';
diff --git a/package.json b/package.json
index 544f1764..eaeec4a2 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/server/controllers/aws.controller.js b/server/controllers/aws.controller.js
index a22be355..2b23e21f 100644
--- a/server/controllers/aws.controller.js
+++ b/server/controllers/aws.controller.js
@@ -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});
+ });
+ });
+ });
+}
diff --git a/server/controllers/project.controller.js b/server/controllers/project.controller.js
index cdfb0ac1..8814740a 100644
--- a/server/controllers/project.controller.js
+++ b/server/controllers/project.controller.js
@@ -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));
diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js
index ac273d34..2e002399 100644
--- a/server/controllers/user.controller.js
+++ b/server/controllers/user.controller.js
@@ -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) {
diff --git a/server/routes/aws.routes.js b/server/routes/aws.routes.js
index 85daddf7..e31457a6 100644
--- a/server/routes/aws.routes.js
+++ b/server/routes/aws.routes.js
@@ -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;
diff --git a/server/routes/server.routes.js b/server/routes/server.routes.js
index 7bd944a2..f4ec6bf2 100644
--- a/server/routes/server.routes.js
+++ b/server/routes/server.routes.js
@@ -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))