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:
parent
33582a1a2e
commit
e86e9a0ae0
11 changed files with 113 additions and 14 deletions
|
@ -101,3 +101,6 @@ export const ERROR = 'ERROR';
|
||||||
|
|
||||||
export const JUST_OPENED_PROJECT = 'JUST_OPENED_PROJECT';
|
export const JUST_OPENED_PROJECT = 'JUST_OPENED_PROJECT';
|
||||||
export const RESET_JUST_OPENED_PROJECT = 'RESET_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';
|
||||||
|
|
|
@ -207,3 +207,16 @@ export function resetJustOpenedProject() {
|
||||||
type: ActionTypes.RESET_JUST_OPENED_PROJECT
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -2,13 +2,18 @@ import * as ActionTypes from '../../../constants';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { showToast, setToastText } from './toast';
|
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';
|
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
||||||
|
|
||||||
export function getProject(id) {
|
export function getProject(id) {
|
||||||
return (dispatch) => {
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
dispatch(justOpenedProject());
|
dispatch(justOpenedProject());
|
||||||
|
if (state.ide.justOpenedProject) {
|
||||||
|
dispatch(resetProjectSavedTime());
|
||||||
|
}
|
||||||
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
|
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
// browserHistory.push(`/projects/${id}`);
|
// browserHistory.push(`/projects/${id}`);
|
||||||
|
@ -46,6 +51,7 @@ export function saveProject(autosave = false) {
|
||||||
axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
|
axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(setUnsavedChanges(false));
|
dispatch(setUnsavedChanges(false));
|
||||||
|
dispatch(setProjectSavedTime(moment().format()));
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionTypes.PROJECT_SAVE_SUCCESS
|
type: ActionTypes.PROJECT_SAVE_SUCCESS
|
||||||
});
|
});
|
||||||
|
@ -69,6 +75,7 @@ export function saveProject(autosave = false) {
|
||||||
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
dispatch(setUnsavedChanges(false));
|
dispatch(setUnsavedChanges(false));
|
||||||
|
dispatch(setProjectSavedTime(moment().format()));
|
||||||
browserHistory.push(`/projects/${response.data.id}`);
|
browserHistory.push(`/projects/${response.data.id}`);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionTypes.NEW_PROJECT,
|
type: ActionTypes.NEW_PROJECT,
|
||||||
|
|
|
@ -28,6 +28,7 @@ const downArrowUrl = require('../../../images/down-arrow.svg');
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
import Timer from '../components/Timer';
|
||||||
|
|
||||||
class Editor extends React.Component {
|
class Editor extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -158,6 +159,13 @@ class Editor extends React.Component {
|
||||||
role="main"
|
role="main"
|
||||||
className={editorSectionClass}
|
className={editorSectionClass}
|
||||||
>
|
>
|
||||||
|
<div className="editor__file-name">
|
||||||
|
<span>{this.props.file.name}
|
||||||
|
{this.props.unsavedChanges ? '*' : null}</span>
|
||||||
|
<Timer
|
||||||
|
projectSavedTime={this.props.projectSavedTime}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
className="editor__options-button"
|
className="editor__options-button"
|
||||||
aria-label="editor options"
|
aria-label="editor options"
|
||||||
|
@ -214,6 +222,8 @@ Editor.propTypes = {
|
||||||
autorefresh: PropTypes.bool.isRequired,
|
autorefresh: PropTypes.bool.isRequired,
|
||||||
isPlaying: PropTypes.bool.isRequired,
|
isPlaying: PropTypes.bool.isRequired,
|
||||||
theme: PropTypes.string.isRequired,
|
theme: PropTypes.string.isRequired,
|
||||||
|
unsavedChanges: PropTypes.bool.isRequired,
|
||||||
|
projectSavedTime: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Editor;
|
export default Editor;
|
||||||
|
|
46
client/modules/IDE/components/Timer.js
Normal file
46
client/modules/IDE/components/Timer.js
Normal 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;
|
|
@ -295,6 +295,7 @@ class IDEView extends React.Component {
|
||||||
stopSketch={this.props.stopSketch}
|
stopSketch={this.props.stopSketch}
|
||||||
autorefresh={this.props.preferences.autorefresh}
|
autorefresh={this.props.preferences.autorefresh}
|
||||||
unsavedChanges={this.props.ide.unsavedChanges}
|
unsavedChanges={this.props.ide.unsavedChanges}
|
||||||
|
projectSavedTime={this.props.ide.projectSavedTime}
|
||||||
/>
|
/>
|
||||||
<Console
|
<Console
|
||||||
consoleEvent={this.props.ide.consoleEvent}
|
consoleEvent={this.props.ide.consoleEvent}
|
||||||
|
@ -477,7 +478,8 @@ IDEView.propTypes = {
|
||||||
unsavedChanges: PropTypes.bool.isRequired,
|
unsavedChanges: PropTypes.bool.isRequired,
|
||||||
infiniteLoop: PropTypes.bool.isRequired,
|
infiniteLoop: PropTypes.bool.isRequired,
|
||||||
previewIsRefreshing: PropTypes.bool.isRequired,
|
previewIsRefreshing: PropTypes.bool.isRequired,
|
||||||
infiniteLoopMessage: PropTypes.string.isRequired
|
infiniteLoopMessage: PropTypes.string.isRequired,
|
||||||
|
projectSavedTime: PropTypes.string.isRequired
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
startSketch: PropTypes.func.isRequired,
|
startSketch: PropTypes.func.isRequired,
|
||||||
stopSketch: PropTypes.func.isRequired,
|
stopSketch: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -17,7 +17,8 @@ const initialState = {
|
||||||
infiniteLoop: false,
|
infiniteLoop: false,
|
||||||
previewIsRefreshing: false,
|
previewIsRefreshing: false,
|
||||||
infiniteLoopMessage: '',
|
infiniteLoopMessage: '',
|
||||||
projectJustOpened: false
|
projectJustOpened: false,
|
||||||
|
projectSavedTime: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
const ide = (state = initialState, action) => {
|
const ide = (state = initialState, action) => {
|
||||||
|
@ -84,6 +85,10 @@ const ide = (state = initialState, action) => {
|
||||||
return Object.assign({}, state, { justOpenedProject: true });
|
return Object.assign({}, state, { justOpenedProject: true });
|
||||||
case ActionTypes.RESET_JUST_OPENED_PROJECT:
|
case ActionTypes.RESET_JUST_OPENED_PROJECT:
|
||||||
return Object.assign({}, state, { justOpenedProject: false });
|
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:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,8 +79,8 @@
|
||||||
@extend %icon;
|
@extend %icon;
|
||||||
}
|
}
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: #{5 / $base-font-size}rem;
|
top: 0;
|
||||||
right: #{20 / $base-font-size}rem; // move left to avoid vertical scroll bar
|
right: #{1.5 / $base-font-size}rem; // move left to avoid vertical scroll bar
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,10 +97,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lint-error {
|
.editor__file-name {
|
||||||
font-family: Inconsolata, monospace;
|
@include themify() {
|
||||||
font-size: 100%;
|
color: getThemifyVariable('inactive-text-color');
|
||||||
background: rgba($console-error-color, 0.3);
|
}
|
||||||
color: $console-error-color;
|
padding: 0 0 #{7 / $base-font-size}rem #{76 / $base-font-size}rem;
|
||||||
padding: 2px 5px 3px;
|
font-size: #{12 / $base-font-size}rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
.sidebar__header {
|
.sidebar {
|
||||||
@include themify() {
|
@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;
|
padding: #{10 / $base-font-size}rem #{6 / $base-font-size}rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
7
client/styles/components/_timer.scss
Normal file
7
client/styles/components/_timer.scss
Normal 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;
|
||||||
|
}
|
|
@ -32,6 +32,7 @@
|
||||||
@import 'components/github-button';
|
@import 'components/github-button';
|
||||||
@import 'components/forms';
|
@import 'components/forms';
|
||||||
@import 'components/toast';
|
@import 'components/toast';
|
||||||
|
@import 'components/timer';
|
||||||
|
|
||||||
@import 'layout/ide';
|
@import 'layout/ide';
|
||||||
@import 'layout/fullscreen';
|
@import 'layout/fullscreen';
|
||||||
|
|
Loading…
Reference in a new issue