mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-10 20:03:03 +00:00
Make autosave common/move save button into common format
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
require('./editPage.less');
|
require('./editPage.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const Navbar = require('../../../navbar/navbar.jsx');
|
const Navbar = require('../../../navbar/navbar.jsx');
|
||||||
@@ -25,9 +26,13 @@ const STYLEKEY = 'homebrewery-new-style';
|
|||||||
const METAKEY = 'homebrewery-new-meta';
|
const METAKEY = 'homebrewery-new-meta';
|
||||||
const SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${global.account?.username || ''}`;
|
const SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${global.account?.username || ''}`;
|
||||||
|
|
||||||
|
const SAVE_TIMEOUT = 10000;
|
||||||
|
|
||||||
const BaseEditPage = (props)=>{
|
const BaseEditPage = (props)=>{
|
||||||
const [brew, setBrew] = useState(() => props.brew);
|
const [brew, setBrew] = useState(() => props.brew);
|
||||||
|
const [savedBrew, setSavedBrew] = useState(brew);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [lastSavedTime, setLastSavedTime] = useState(new Date());
|
||||||
const [saveGoogle, setSaveGoogle] = useState(() => (global.account?.googleId ? true : false));
|
const [saveGoogle, setSaveGoogle] = useState(() => (global.account?.googleId ? true : false));
|
||||||
const [welcomeText, setWelcomeText] = useState(() => props.brew?.text ?? '');
|
const [welcomeText, setWelcomeText] = useState(() => props.brew?.text ?? '');
|
||||||
const [error, setError] = useState(undefined);
|
const [error, setError] = useState(undefined);
|
||||||
@@ -36,8 +41,14 @@ const BaseEditPage = (props)=>{
|
|||||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||||
const [themeBundle, setThemeBundle] = useState({});
|
const [themeBundle, setThemeBundle] = useState({});
|
||||||
|
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
||||||
|
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true);
|
||||||
|
const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(false);
|
||||||
|
|
||||||
const editorRef = useRef(null);
|
const editorRef = useRef(null);
|
||||||
|
let lastSavedBrew = useRef(JSON.parse(JSON.stringify(this.propcopys.brew))); //Deep copy
|
||||||
|
const saveTimeout = useRef(null);
|
||||||
|
const unsavedChangesTimer = useRef(null);
|
||||||
|
|
||||||
const handleSplitMove = ()=>{
|
const handleSplitMove = ()=>{
|
||||||
editorRef.current.update();
|
editorRef.current.update();
|
||||||
@@ -75,6 +86,11 @@ const BaseEditPage = (props)=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSnipChange = (snippet)=>{
|
const handleSnipChange = (snippet)=>{
|
||||||
|
//If there are HTML errors, run the validator on every change to give quick feedback
|
||||||
|
if(htmlErrors.length)
|
||||||
|
htmlErrors = Markdown.validate(text);
|
||||||
|
|
||||||
|
setHTMLErrors(htmlErrors);
|
||||||
setBrew((prevBrew) => ({ ...prevBrew, snippets: snippet }));
|
setBrew((prevBrew) => ({ ...prevBrew, snippets: snippet }));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,19 +107,39 @@ const BaseEditPage = (props)=>{
|
|||||||
'lang' : metadata.lang
|
'lang' : metadata.lang
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateBrew = (newData) => {
|
||||||
|
setBrew(prevBrew => ({ //TODO: May be able to just directly use setBrew instead of a wrapper, if its safe to assume we want all the data from newData
|
||||||
|
...prevBrew, //OR: Somehow combine handleTextChange, handleStyleChange, handleMetaChange, and handleSnipChange into one function that calls this
|
||||||
|
style: newData.style,
|
||||||
|
text: newData.text,
|
||||||
|
snippets: newData.snippets
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const clearError = ()=>{
|
const clearError = ()=>{
|
||||||
setError(null);
|
setError(null);
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const save = async ()=>{
|
const save = async (immediate=false)=>{
|
||||||
setIsSaving(true);
|
if(isSaving) return;
|
||||||
await props.performSave(brew, saveGoogle)
|
if(!unsavedChanges && !immediate) return;
|
||||||
.catch((err)=>{
|
|
||||||
setError(err);
|
clearTimeout(saveTimeout.current);
|
||||||
});
|
|
||||||
setIsSaving(false)
|
const timeout = immediate ? 0 : 10000;
|
||||||
|
|
||||||
|
saveTimeout.current = setTimeout(async () => {
|
||||||
|
setIsSaving(true);
|
||||||
|
await props.performSave(brew, saveGoogle)
|
||||||
|
.catch((err)=>{
|
||||||
|
setError(err);
|
||||||
|
});
|
||||||
|
setIsSaving(false);
|
||||||
|
setLastSavedTime(new Date());
|
||||||
|
setTimeout(setWarnUnsavedChanges(true), 900000); // 15 minutes between unsaved work warnings
|
||||||
|
}, timeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleControlKeys = (e)=>{
|
const handleControlKeys = (e)=>{
|
||||||
@@ -118,17 +154,89 @@ const BaseEditPage = (props)=>{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasChanges =()=>{
|
||||||
|
return !_.isEqual(brew, savedBrew);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
props.loadBrew?.(brew, setBrew, setSaveGoogle); //Initial load from localStorage/etc.
|
props.loadBrew?.(brew, setBrew, setSaveGoogle); //Initial load from localStorage/etc.
|
||||||
|
|
||||||
|
//Load settings
|
||||||
|
setAutoSaveEnabled(JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true);
|
||||||
|
|
||||||
|
setHTMLErrors(Markdown.validate(brew.text));
|
||||||
|
|
||||||
document.addEventListener('keydown', handleControlKeys);
|
document.addEventListener('keydown', handleControlKeys);
|
||||||
return document.removeEventListener('keydown', handleControlKeys);
|
window.onbeforeunload = ()=>{
|
||||||
|
if(isSaving || unsavedChanges)
|
||||||
|
return 'You have unsaved changes!';
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleControlKeys);
|
||||||
|
window.onbeforeunload = null;
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchThemeBundle(setError, setThemeBundle, brew.renderer, brew.theme);
|
fetchThemeBundle(setError, setThemeBundle, brew.renderer, brew.theme);
|
||||||
}, [brew.renderer, brew.theme]);
|
}, [brew.renderer, brew.theme]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const hasChange = hasChanges();
|
||||||
|
if(unsavedChanges !== hasChange)
|
||||||
|
setUnsavedChanges(hasChange);
|
||||||
|
|
||||||
|
if(autoSaveEnabled) save();
|
||||||
|
}, [brew]);
|
||||||
|
|
||||||
|
const resetUnsavedChangesWarning = ()=>{
|
||||||
|
setTimeout(setWarnUnsavedChanges(false), 4000); // Display warning for 4 seconds
|
||||||
|
setTimeout(setWarnUnsavedChanges(true) , 90000); // 15 minutes between warnings
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleAutoSave = ()=>{
|
||||||
|
if(unsavedChangesTimer.current) clearTimeout(unsavedChangesTimer.current);
|
||||||
|
localStorage.setItem('AUTOSAVE_ON', JSON.stringify(!autoSaveEnabled));
|
||||||
|
setAutoSaveEnabled(!autoSaveEnabled);
|
||||||
|
setWarnUnsavedChanges(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSaveButton = ()=>{
|
||||||
|
// #1 - Currently saving, show SAVING
|
||||||
|
if(isSaving)
|
||||||
|
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
|
||||||
|
|
||||||
|
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
|
||||||
|
if(unsavedChanges && warnUnsavedChanges){
|
||||||
|
resetUnsavedChangesWarning();
|
||||||
|
const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
|
||||||
|
const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
|
||||||
|
|
||||||
|
return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
|
||||||
|
Reminder...
|
||||||
|
<div className='errorContainer'>{text}</div>
|
||||||
|
</Nav.item>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #3 - Unsaved changes exist, click to save, show SAVE NOW
|
||||||
|
if(unsavedChanges)
|
||||||
|
return <Nav.item className='save' onClick={()=>save(true)} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
|
||||||
|
|
||||||
|
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
|
||||||
|
if(autoSaveEnabled)
|
||||||
|
return <Nav.item className='save saved'>auto-saved.</Nav.item>;
|
||||||
|
|
||||||
|
// DEFAULT - No unsaved changes, show SAVED
|
||||||
|
return <Nav.item className='save saved'>saved.</Nav.item>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderAutoSaveButton = ()=>{
|
||||||
|
return <Nav.item onClick={toggleAutoSave}>
|
||||||
|
Autosave <i className={autosaveEnabled ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
|
||||||
|
</Nav.item>;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`sitePage ${props.className || ''}`}>
|
<div className={`sitePage ${props.className || ''}`}>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
@@ -138,7 +246,10 @@ const BaseEditPage = (props)=>{
|
|||||||
<Nav.section>
|
<Nav.section>
|
||||||
{error
|
{error
|
||||||
? <ErrorNavItem error={error} clearError={clearError}></ErrorNavItem>
|
? <ErrorNavItem error={error} clearError={clearError}></ErrorNavItem>
|
||||||
: props.saveButton?.(save, isSaving)
|
: <Nav.dropdown className='save-menu'>
|
||||||
|
{renderSaveButton()}
|
||||||
|
{renderAutoSaveButton()}
|
||||||
|
</Nav.dropdown>
|
||||||
}
|
}
|
||||||
{props.renderUniqueNav?.()}
|
{props.renderUniqueNav?.()}
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
@@ -160,10 +271,13 @@ const BaseEditPage = (props)=>{
|
|||||||
onTextChange={handleTextChange}
|
onTextChange={handleTextChange}
|
||||||
onStyleChange={handleStyleChange}
|
onStyleChange={handleStyleChange}
|
||||||
onMetaChange={handleMetaChange}
|
onMetaChange={handleMetaChange}
|
||||||
|
onSnipChange={handleSnipChange}
|
||||||
|
reportError={this.errorReported}
|
||||||
renderer={brew.renderer}
|
renderer={brew.renderer}
|
||||||
showEditButtons={false} //FALSE FOR HOME PAGE
|
showEditButtons={false} //FALSE FOR HOME PAGE
|
||||||
userThemes={props.userThemes}
|
userThemes={props.userThemes}
|
||||||
themeBundle={themeBundle}
|
themeBundle={themeBundle}
|
||||||
|
updateBrew={updateBrew}
|
||||||
onCursorPageChange={handleEditorCursorPageChange}
|
onCursorPageChange={handleEditorCursorPageChange}
|
||||||
onViewPageChange={handleEditorViewPageChange}
|
onViewPageChange={handleEditorViewPageChange}
|
||||||
currentEditorViewPageNum={currentEditorViewPageNum}
|
currentEditorViewPageNum={currentEditorViewPageNum}
|
||||||
@@ -187,7 +301,7 @@ const BaseEditPage = (props)=>{
|
|||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{props.children?.(welcomeText, brew.text, save)}
|
{props.children?.(welcomeText, brew, save)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ const { Meta } = require('vitreum/headtags');
|
|||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
|
||||||
|
|
||||||
const BaseEditPage = require('../basePages/editPage/editPage.jsx');
|
const BaseEditPage = require('../basePages/editPage/editPage.jsx');
|
||||||
const SplitPane = require('client/components/splitPane/splitPane.jsx');
|
|
||||||
const Editor = require('../../editor/editor.jsx');
|
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
const LockNotification = require('./lockNotification/lockNotification.jsx');
|
const LockNotification = require('./lockNotification/lockNotification.jsx');
|
||||||
|
|
||||||
@@ -30,7 +25,6 @@ import { updateHistory, versionHistoryGarbageCollection } from '../../utils/vers
|
|||||||
|
|
||||||
const googleDriveIcon = require('../../googleDrive.svg');
|
const googleDriveIcon = require('../../googleDrive.svg');
|
||||||
|
|
||||||
const SAVE_TIMEOUT = 10000;
|
|
||||||
|
|
||||||
const EditPage = createClass({
|
const EditPage = createClass({
|
||||||
displayName : 'EditPage',
|
displayName : 'EditPage',
|
||||||
@@ -42,171 +36,17 @@ const EditPage = createClass({
|
|||||||
|
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
return {
|
return {
|
||||||
brew : this.props.brew,
|
|
||||||
isSaving : false,
|
|
||||||
unsavedChanges : false,
|
|
||||||
alertTrashedGoogleBrew : this.props.brew.trashed,
|
alertTrashedGoogleBrew : this.props.brew.trashed,
|
||||||
alertLoginToTransfer : false,
|
alertLoginToTransfer : false,
|
||||||
saveGoogle : this.props.brew.googleId ? true : false,
|
saveGoogle : this.props.brew.googleId ? true : false,
|
||||||
confirmGoogleTransfer : false,
|
confirmGoogleTransfer : false,
|
||||||
error : null,
|
error : null,
|
||||||
htmlErrors : Markdown.validate(this.props.brew.text),
|
|
||||||
url : '',
|
|
||||||
autoSave : true,
|
|
||||||
autoSaveWarning : false,
|
|
||||||
unsavedTime : new Date(),
|
|
||||||
currentEditorViewPageNum : 1,
|
|
||||||
currentEditorCursorPageNum : 1,
|
|
||||||
currentBrewRendererPageNum : 1,
|
|
||||||
displayLockMessage : this.props.brew.lock || false,
|
displayLockMessage : this.props.brew.lock || false,
|
||||||
themeBundle : {}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
editor : React.createRef(null),
|
|
||||||
savedBrew : null,
|
|
||||||
|
|
||||||
componentDidMount : function(){
|
componentDidMount : function(){
|
||||||
this.setState({
|
|
||||||
url : window.location.href
|
|
||||||
});
|
|
||||||
|
|
||||||
this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy
|
|
||||||
|
|
||||||
this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{
|
|
||||||
if(this.state.autoSave){
|
|
||||||
this.trySave();
|
|
||||||
} else {
|
|
||||||
this.setState({ autoSaveWarning: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.onbeforeunload = ()=>{
|
|
||||||
if(this.state.isSaving || this.state.unsavedChanges){
|
|
||||||
return 'You have unsaved changes!';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setState((prevState)=>({
|
|
||||||
htmlErrors : Markdown.validate(prevState.brew.text)
|
|
||||||
}));
|
|
||||||
|
|
||||||
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
|
|
||||||
|
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
|
||||||
},
|
|
||||||
componentWillUnmount : function() {
|
|
||||||
window.onbeforeunload = function(){};
|
|
||||||
document.removeEventListener('keydown', this.handleControlKeys);
|
|
||||||
},
|
|
||||||
componentDidUpdate : function(){
|
|
||||||
const hasChange = this.hasChanges();
|
|
||||||
if(this.state.unsavedChanges != hasChange){
|
|
||||||
this.setState({
|
|
||||||
unsavedChanges : hasChange
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleControlKeys : function(e){
|
|
||||||
if(!(e.ctrlKey || e.metaKey)) return;
|
|
||||||
const S_KEY = 83;
|
|
||||||
const P_KEY = 80;
|
|
||||||
if(e.keyCode == S_KEY) this.trySave(true);
|
|
||||||
if(e.keyCode == P_KEY) printCurrentBrew();
|
|
||||||
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSplitMove : function(){
|
|
||||||
this.editor.current.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEditorViewPageChange : function(pageNumber){
|
|
||||||
this.setState({ currentEditorViewPageNum: pageNumber });
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEditorCursorPageChange : function(pageNumber){
|
|
||||||
this.setState({ currentEditorCursorPageNum: pageNumber });
|
|
||||||
},
|
|
||||||
|
|
||||||
handleBrewRendererPageChange : function(pageNumber){
|
|
||||||
this.setState({ currentBrewRendererPageNum: pageNumber });
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextChange : function(text){
|
|
||||||
//If there are errors, run the validator on every change to give quick feedback
|
|
||||||
let htmlErrors = this.state.htmlErrors;
|
|
||||||
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
|
||||||
|
|
||||||
this.setState((prevState)=>({
|
|
||||||
brew : { ...prevState.brew, text: text },
|
|
||||||
htmlErrors : htmlErrors,
|
|
||||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSnipChange : function(snippet){
|
|
||||||
//If there are errors, run the validator on every change to give quick feedback
|
|
||||||
let htmlErrors = this.state.htmlErrors;
|
|
||||||
if(htmlErrors.length) htmlErrors = Markdown.validate(snippet);
|
|
||||||
|
|
||||||
this.setState((prevState)=>({
|
|
||||||
brew : { ...prevState.brew, snippets: snippet },
|
|
||||||
unsavedChanges : true,
|
|
||||||
htmlErrors : htmlErrors,
|
|
||||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleStyleChange : function(style){
|
|
||||||
this.setState((prevState)=>({
|
|
||||||
brew : { ...prevState.brew, style: style }
|
|
||||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMetaChange : function(metadata, field=undefined){
|
|
||||||
if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed
|
|
||||||
fetchThemeBundle(this, metadata.renderer, metadata.theme);
|
|
||||||
|
|
||||||
this.setState((prevState)=>({
|
|
||||||
brew : {
|
|
||||||
...prevState.brew,
|
|
||||||
...metadata
|
|
||||||
}
|
|
||||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
|
||||||
},
|
|
||||||
|
|
||||||
hasChanges : function(){
|
|
||||||
return !_.isEqual(this.state.brew, this.savedBrew);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateBrew : function(newData){
|
|
||||||
this.setState((prevState)=>({
|
|
||||||
brew : {
|
|
||||||
...prevState.brew,
|
|
||||||
style : newData.style,
|
|
||||||
text : newData.text,
|
|
||||||
snippets : newData.snippets
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
trySave : function(immediate=false){
|
|
||||||
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
|
||||||
if(this.state.isSaving)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(immediate) {
|
|
||||||
this.debounceSave();
|
|
||||||
this.debounceSave.flush();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.hasChanges())
|
|
||||||
this.debounceSave();
|
|
||||||
else
|
|
||||||
this.debounceSave.cancel();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleGoogleClick : function(){
|
handleGoogleClick : function(){
|
||||||
@@ -217,11 +57,10 @@ const EditPage = createClass({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
confirmGoogleTransfer : !prevState.confirmGoogleTransfer
|
confirmGoogleTransfer : !prevState.confirmGoogleTransfer,
|
||||||
|
error : null
|
||||||
}));
|
}));
|
||||||
this.setState({
|
|
||||||
error : null
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
closeAlerts : function(event){
|
closeAlerts : function(event){
|
||||||
@@ -297,7 +136,6 @@ const EditPage = createClass({
|
|||||||
version : res.body.version
|
version : res.body.version
|
||||||
},
|
},
|
||||||
isSaving : false,
|
isSaving : false,
|
||||||
unsavedTime : new Date()
|
|
||||||
}), ()=>{
|
}), ()=>{
|
||||||
this.setState({ unsavedChanges : this.hasChanges() });
|
this.setState({ unsavedChanges : this.hasChanges() });
|
||||||
});
|
});
|
||||||
@@ -330,7 +168,7 @@ const EditPage = createClass({
|
|||||||
You must be signed in to a Google account to transfer
|
You must be signed in to a Google account to transfer
|
||||||
between the homebrewery and Google Drive!
|
between the homebrewery and Google Drive!
|
||||||
<a target='_blank' rel='noopener noreferrer'
|
<a target='_blank' rel='noopener noreferrer'
|
||||||
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
href={`https://www.naturalcrit.com/login?redirect=${window.Location.href}`}>
|
||||||
<div className='confirm'>
|
<div className='confirm'>
|
||||||
Sign In
|
Sign In
|
||||||
</div>
|
</div>
|
||||||
@@ -352,68 +190,6 @@ const EditPage = createClass({
|
|||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderSaveButton : function(){
|
|
||||||
|
|
||||||
// #1 - Currently saving, show SAVING
|
|
||||||
if(this.state.isSaving){
|
|
||||||
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
|
|
||||||
if(this.state.unsavedChanges && this.state.autoSaveWarning){
|
|
||||||
this.setAutosaveWarning();
|
|
||||||
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
|
|
||||||
const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
|
|
||||||
|
|
||||||
return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
|
|
||||||
Reminder...
|
|
||||||
<div className='errorContainer'>
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
</Nav.item>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #3 - Unsaved changes exist, click to save, show SAVE NOW
|
|
||||||
// Use trySave(true) instead of save() to use debounced save function
|
|
||||||
if(this.state.unsavedChanges){
|
|
||||||
return <Nav.item className='save' onClick={()=>this.trySave(true)} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
|
|
||||||
}
|
|
||||||
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
|
|
||||||
if(this.state.autoSave){
|
|
||||||
return <Nav.item className='save saved'>auto-saved.</Nav.item>;
|
|
||||||
}
|
|
||||||
// DEFAULT - No unsaved changes, show SAVED
|
|
||||||
return <Nav.item className='save saved'>saved.</Nav.item>;
|
|
||||||
},
|
|
||||||
|
|
||||||
handleAutoSave : function(){
|
|
||||||
if(this.warningTimer) clearTimeout(this.warningTimer);
|
|
||||||
this.setState((prevState)=>({
|
|
||||||
autoSave : !prevState.autoSave,
|
|
||||||
autoSaveWarning : prevState.autoSave
|
|
||||||
}), ()=>{
|
|
||||||
localStorage.setItem('AUTOSAVE_ON', JSON.stringify(this.state.autoSave));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setAutosaveWarning : function(){
|
|
||||||
setTimeout(()=>this.setState({ autoSaveWarning: false }), 4000); // 4 seconds to display
|
|
||||||
this.warningTimer = setTimeout(()=>{this.setState({ autoSaveWarning: true });}, 900000); // 15 minutes between warnings
|
|
||||||
this.warningTimer;
|
|
||||||
},
|
|
||||||
|
|
||||||
errorReported : function(error) {
|
|
||||||
this.setState({
|
|
||||||
error
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderAutoSaveButton : function(){
|
|
||||||
return <Nav.item onClick={this.handleAutoSave}>
|
|
||||||
Autosave <i className={this.state.autoSave ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
|
|
||||||
</Nav.item>;
|
|
||||||
},
|
|
||||||
|
|
||||||
processShareId : function() {
|
processShareId : function() {
|
||||||
return this.state.brew.googleId && !this.state.brew.stubbed ?
|
return this.state.brew.googleId && !this.state.brew.stubbed ?
|
||||||
this.state.brew.googleId + this.state.brew.shareId :
|
this.state.brew.googleId + this.state.brew.shareId :
|
||||||
@@ -421,7 +197,6 @@ const EditPage = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getRedditLink : function(){
|
getRedditLink : function(){
|
||||||
|
|
||||||
const shareLink = this.processShareId();
|
const shareLink = this.processShareId();
|
||||||
const systems = this.props.brew.systems.length > 0 ? ` [${this.props.brew.systems.join(' - ')}]` : '';
|
const systems = this.props.brew.systems.length > 0 ? ` [${this.props.brew.systems.join(' - ')}]` : '';
|
||||||
const title = `${this.props.brew.title} ${systems}`;
|
const title = `${this.props.brew.title} ${systems}`;
|
||||||
@@ -438,13 +213,6 @@ const EditPage = createClass({
|
|||||||
return <>
|
return <>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
{this.renderGoogleDriveIcon()}
|
{this.renderGoogleDriveIcon()}
|
||||||
{this.state.error ?
|
|
||||||
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
|
|
||||||
<Nav.dropdown className='save-menu'>
|
|
||||||
{this.renderSaveButton()}
|
|
||||||
{this.renderAutoSaveButton()}
|
|
||||||
</Nav.dropdown>
|
|
||||||
}
|
|
||||||
<Nav.dropdown>
|
<Nav.dropdown>
|
||||||
<Nav.item color='teal' icon='fas fa-share-alt'>
|
<Nav.item color='teal' icon='fas fa-share-alt'>
|
||||||
share
|
share
|
||||||
@@ -464,52 +232,21 @@ const EditPage = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <BaseEditPage
|
return <BaseEditPage
|
||||||
className="editPage"
|
className="editPage"
|
||||||
errorState={this.state.error}
|
errorState={this.state.error}
|
||||||
parent={this}
|
parent={this}
|
||||||
brew={this.state.brew}
|
brew={this.state.brew}
|
||||||
navButtons={this.renderNavbar()}
|
renderUniqueNav={this.renderNavbar}
|
||||||
|
performSave={this.save}
|
||||||
recentStorageKey='edit'>
|
recentStorageKey='edit'>
|
||||||
<Meta name='robots' content='noindex, nofollow' />
|
{(welcomeText, brew, save) => {
|
||||||
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} reviewRequested={this.props.brew.lock.reviewRequested} />}
|
return <>
|
||||||
<div className='content'>
|
<Meta name='robots' content='noindex, nofollow' />
|
||||||
<SplitPane onDragFinish={this.handleSplitMove}>
|
{this.props.brew.lock && <LockNotification shareId={brew.shareId} message={brew.lock.editMessage} reviewRequested={brew.lock.reviewRequested} />}
|
||||||
<Editor
|
</>
|
||||||
ref={this.editor}
|
}}
|
||||||
brew={this.state.brew}
|
</BaseEditPage>;
|
||||||
onTextChange={this.handleTextChange}
|
|
||||||
onStyleChange={this.handleStyleChange}
|
|
||||||
onSnipChange={this.handleSnipChange}
|
|
||||||
onMetaChange={this.handleMetaChange}
|
|
||||||
reportError={this.errorReported}
|
|
||||||
renderer={this.state.brew.renderer}
|
|
||||||
userThemes={this.props.userThemes}
|
|
||||||
themeBundle={this.state.themeBundle}
|
|
||||||
updateBrew={this.updateBrew}
|
|
||||||
onCursorPageChange={this.handleEditorCursorPageChange}
|
|
||||||
onViewPageChange={this.handleEditorViewPageChange}
|
|
||||||
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
|
||||||
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
|
|
||||||
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
|
|
||||||
/>
|
|
||||||
<BrewRenderer
|
|
||||||
text={this.state.brew.text}
|
|
||||||
style={this.state.brew.style}
|
|
||||||
renderer={this.state.brew.renderer}
|
|
||||||
theme={this.state.brew.theme}
|
|
||||||
themeBundle={this.state.themeBundle}
|
|
||||||
errors={this.state.htmlErrors}
|
|
||||||
lang={this.state.brew.lang}
|
|
||||||
onPageChange={this.handleBrewRendererPageChange}
|
|
||||||
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
|
||||||
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
|
|
||||||
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
|
|
||||||
allowPrint={true}
|
|
||||||
/>
|
|
||||||
</SplitPane>
|
|
||||||
</div>
|
|
||||||
</BaseEditPage>;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -30,25 +30,24 @@ const HomePage = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <BaseEditPage
|
return <BaseEditPage
|
||||||
{...this.props}
|
{...this.props}
|
||||||
className="homePage"
|
className="homePage"
|
||||||
parent={this}
|
parent={this}
|
||||||
performSave={this.save}
|
performSave={this.save}>
|
||||||
>
|
{(welcomeText, brew, save) => {
|
||||||
{(welcomeText, brewText, save) => {
|
return <>
|
||||||
return <>
|
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
|
||||||
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
|
<div className={cx('floatingSaveButton', { show: welcomeText != brew.text })} onClick={save}>
|
||||||
<div className={cx('floatingSaveButton', { show: welcomeText != brewText })} onClick={save}>
|
Save current <i className='fas fa-save' />
|
||||||
Save current <i className='fas fa-save' />
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<a href='/new' className='floatingNewButton'>
|
<a href='/new' className='floatingNewButton'>
|
||||||
Create your own <i className='fas fa-magic' />
|
Create your own <i className='fas fa-magic' />
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
}}
|
}}
|
||||||
</BaseEditPage>
|
</BaseEditPage>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -66,27 +66,14 @@ const NewPage = createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renderSaveButton : function(save, isSaving){
|
|
||||||
if(isSaving){
|
|
||||||
return <Nav.item icon='fas fa-spinner fa-spin' className='save'>
|
|
||||||
save...
|
|
||||||
</Nav.item>;
|
|
||||||
} else {
|
|
||||||
return <Nav.item icon='fas fa-save' className='save' onClick={save}>
|
|
||||||
save
|
|
||||||
</Nav.item>;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <BaseEditPage
|
return <BaseEditPage
|
||||||
{...this.props}
|
{...this.props}
|
||||||
className="newPage"
|
className="newPage"
|
||||||
parent={this}
|
parent={this}
|
||||||
saveButton={this.renderSaveButton}
|
|
||||||
performSave={this.save}
|
performSave={this.save}
|
||||||
loadBrew={this.loadBrew}>
|
loadBrew={this.loadBrew}>
|
||||||
</BaseEditPage>;
|
</BaseEditPage>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user