Added a star icon for unsaved file name, added 'Saved: xx time ago' (#177)

* added a star icon for unsaved file name, added saved time ago

* changed text

* added timer component

* clean extra styling in _editor.sass

* customize momentjs fromnow function

* clear 10s interval in componentWillUnmount

* use space-between instead of float
This commit is contained in:
Yining Shi 2016-11-09 12:52:14 -05:00 committed by Cassie Tarakajian
parent 33582a1a2e
commit e86e9a0ae0
11 changed files with 113 additions and 14 deletions

View file

@ -101,3 +101,6 @@ export const ERROR = 'ERROR';
export const JUST_OPENED_PROJECT = 'JUST_OPENED_PROJECT';
export const RESET_JUST_OPENED_PROJECT = 'RESET_JUST_OPENED_PROJECT';
export const SET_PROJECT_SAVED_TIME = 'SET_PROJECT_SAVED_TIME';
export const RESET_PROJECT_SAVED_TIME = 'RESET_PROJECT_SAVED_TIME';

View file

@ -207,3 +207,16 @@ export function resetJustOpenedProject() {
type: ActionTypes.RESET_JUST_OPENED_PROJECT
};
}
export function setProjectSavedTime(value) {
return {
type: ActionTypes.SET_PROJECT_SAVED_TIME,
value
};
}
export function resetProjectSavedTime() {
return {
type: ActionTypes.RESET_PROJECT_SAVED_TIME,
};
}

View file

@ -2,13 +2,18 @@ import * as ActionTypes from '../../../constants';
import { browserHistory } from 'react-router';
import axios from 'axios';
import { showToast, setToastText } from './toast';
import { setUnsavedChanges, justOpenedProject, resetJustOpenedProject } from './ide';
import { setUnsavedChanges, justOpenedProject, resetJustOpenedProject, setProjectSavedTime, resetProjectSavedTime } from './ide';
import moment from 'moment';
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
export function getProject(id) {
return (dispatch) => {
return (dispatch, getState) => {
const state = getState();
dispatch(justOpenedProject());
if (state.ide.justOpenedProject) {
dispatch(resetProjectSavedTime());
}
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
.then(response => {
// browserHistory.push(`/projects/${id}`);
@ -46,6 +51,7 @@ export function saveProject(autosave = false) {
axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
.then(() => {
dispatch(setUnsavedChanges(false));
dispatch(setProjectSavedTime(moment().format()));
dispatch({
type: ActionTypes.PROJECT_SAVE_SUCCESS
});
@ -69,6 +75,7 @@ export function saveProject(autosave = false) {
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
.then(response => {
dispatch(setUnsavedChanges(false));
dispatch(setProjectSavedTime(moment().format()));
browserHistory.push(`/projects/${response.data.id}`);
dispatch({
type: ActionTypes.NEW_PROJECT,

View file

@ -28,6 +28,7 @@ const downArrowUrl = require('../../../images/down-arrow.svg');
import classNames from 'classnames';
import { debounce } from 'lodash';
import Timer from '../components/Timer';
class Editor extends React.Component {
constructor(props) {
@ -158,6 +159,13 @@ class Editor extends React.Component {
role="main"
className={editorSectionClass}
>
<div className="editor__file-name">
<span>{this.props.file.name}
{this.props.unsavedChanges ? '*' : null}</span>
<Timer
projectSavedTime={this.props.projectSavedTime}
/>
</div>
<button
className="editor__options-button"
aria-label="editor options"
@ -214,6 +222,8 @@ Editor.propTypes = {
autorefresh: PropTypes.bool.isRequired,
isPlaying: PropTypes.bool.isRequired,
theme: PropTypes.string.isRequired,
unsavedChanges: PropTypes.bool.isRequired,
projectSavedTime: PropTypes.string.isRequired
};
export default Editor;

View file

@ -0,0 +1,46 @@
import React, { PropTypes } from 'react';
import moment from 'moment';
class Timer extends React.Component {
constructor(props) {
super(props);
this.showSavedTime = this.showSavedTime.bind(this);
}
componentDidMount() {
this.interval = setInterval(() => this.forceUpdate(), 10000);
}
componentWillUnmount() {
if (this.interval) {
clearInterval(this.interval);
}
}
showSavedTime() {
if (Math.abs(moment().diff(this.props.projectSavedTime)) < 10000) {
return 'Saved: just now';
} else if (Math.abs(moment().diff(this.props.projectSavedTime)) < 20000) {
return 'Saved: 15 seconds ago';
} else if (Math.abs(moment().diff(this.props.projectSavedTime)) < 30000) {
return 'Saved: 25 seconds ago';
} else if (Math.abs(moment().diff(this.props.projectSavedTime)) < 46000) {
return 'Saved: 35 seconds ago';
}
return `Saved: ${moment(this.props.projectSavedTime).fromNow()}`;
}
render() {
return (
<span className="timer__saved-time">
{this.props.projectSavedTime !== '' ? this.showSavedTime() : null}
</span>
);
}
}
Timer.propTypes = {
projectSavedTime: PropTypes.string.isRequired
};
export default Timer;

View file

@ -295,6 +295,7 @@ class IDEView extends React.Component {
stopSketch={this.props.stopSketch}
autorefresh={this.props.preferences.autorefresh}
unsavedChanges={this.props.ide.unsavedChanges}
projectSavedTime={this.props.ide.projectSavedTime}
/>
<Console
consoleEvent={this.props.ide.consoleEvent}
@ -477,7 +478,8 @@ IDEView.propTypes = {
unsavedChanges: PropTypes.bool.isRequired,
infiniteLoop: PropTypes.bool.isRequired,
previewIsRefreshing: PropTypes.bool.isRequired,
infiniteLoopMessage: PropTypes.string.isRequired
infiniteLoopMessage: PropTypes.string.isRequired,
projectSavedTime: PropTypes.string.isRequired
}).isRequired,
startSketch: PropTypes.func.isRequired,
stopSketch: PropTypes.func.isRequired,

View file

@ -17,7 +17,8 @@ const initialState = {
infiniteLoop: false,
previewIsRefreshing: false,
infiniteLoopMessage: '',
projectJustOpened: false
projectJustOpened: false,
projectSavedTime: ''
};
const ide = (state = initialState, action) => {
@ -84,6 +85,10 @@ const ide = (state = initialState, action) => {
return Object.assign({}, state, { justOpenedProject: true });
case ActionTypes.RESET_JUST_OPENED_PROJECT:
return Object.assign({}, state, { justOpenedProject: false });
case ActionTypes.SET_PROJECT_SAVED_TIME:
return Object.assign({}, state, { projectSavedTime: action.value });
case ActionTypes.RESET_PROJECT_SAVED_TIME:
return Object.assign({}, state, { projectSavedTime: '' });
default:
return state;
}

View file

@ -79,8 +79,8 @@
@extend %icon;
}
position: absolute;
top: #{5 / $base-font-size}rem;
right: #{20 / $base-font-size}rem; // move left to avoid vertical scroll bar
top: 0;
right: #{1.5 / $base-font-size}rem; // move left to avoid vertical scroll bar
z-index: 1;
}
@ -97,10 +97,12 @@
}
}
.lint-error {
font-family: Inconsolata, monospace;
font-size: 100%;
background: rgba($console-error-color, 0.3);
color: $console-error-color;
padding: 2px 5px 3px;
.editor__file-name {
@include themify() {
color: getThemifyVariable('inactive-text-color');
}
padding: 0 0 #{7 / $base-font-size}rem #{76 / $base-font-size}rem;
font-size: #{12 / $base-font-size}rem;
display: flex;
justify-content: space-between;
}

View file

@ -1,7 +1,10 @@
.sidebar__header {
.sidebar {
@include themify() {
border-top: 1px solid map-get($theme-map, 'ide-border-color');
border: 1px solid map-get($theme-map, 'ide-border-color');
}
}
.sidebar__header {
padding: #{10 / $base-font-size}rem #{6 / $base-font-size}rem;
display: flex;
justify-content: space-between;

View file

@ -0,0 +1,7 @@
.timer__saved-time {
@include themify() {
color: getThemifyVariable('inactive-text-color');
}
font-size: #{12 / $base-font-size}rem;
padding-right: #{30 / $base-font-size}rem;
}

View file

@ -32,6 +32,7 @@
@import 'components/github-button';
@import 'components/forms';
@import 'components/toast';
@import 'components/timer';
@import 'layout/ide';
@import 'layout/fullscreen';