Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/only sync when online #607

Merged
merged 4 commits into from
Dec 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.org
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ a 'gift' icon appear on the top right corner.
- https://github.com/200ok-ch/organice/pull/574
- https://github.com/200ok-ch/organice/pull/575
- https://github.com/200ok-ch/organice/pull/570
- https://github.com/200ok-ch/organice/pull/607

* [2020-11-21 Sat]

Expand Down
11 changes: 11 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
persistField,
getPersistedField,
} from './util/settings_persister';

import runAllMigrations from './migrations';
import parseQueryString from './util/parse_query_string';
import { BrowserRouter as Router } from 'react-router-dom';
Expand All @@ -27,6 +28,12 @@ import './base.css';

import Entry from './components/Entry';

import {
listenToBrowserButtons,
syncOnBecomingVisible,
listenToNetworkConnectionEvents,
} from './lib/initial_setup';

import _ from 'lodash';
import { Map } from 'immutable';

Expand Down Expand Up @@ -140,6 +147,10 @@ export default class App extends PureComponent {
// Initially load the sample file.
this.store.dispatch(restoreStaticFile('sample'));

listenToBrowserButtons(this.store);
syncOnBecomingVisible(this.store);
listenToNetworkConnectionEvents(this.store);

_.bindAll(this, ['handleDragEnd']);
}

Expand Down
5 changes: 5 additions & 0 deletions src/actions/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export const setIsLoading = (isLoading, path) => ({
path,
});

export const setIsOnline = (online) => ({
type: 'SET_IS_ONLINE',
online,
});

export const setDisappearingLoadingMessage = (loadingMessage, delay) => (dispatch) => {
dispatch(setLoadingMessage(loadingMessage));
setTimeout(() => dispatch(hideLoadingMessage()), delay);
Expand Down
24 changes: 14 additions & 10 deletions src/actions/org.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,20 @@ const syncDebounced = (dispatch, getState, options) => {
};

export const sync = (options) => (dispatch, getState) => {
// If the user hits the 'sync' button, no matter if there's a sync
// in progress or if the sync 'should' be debounced, listen to the
// user and start a sync.
if (options.forceAction === 'manual') {
console.log('forcing sync');
const files = getState().org.present.get('files');
// sync all files on manual sync
files.keySeq().forEach((path) => dispatch(doSync({ ...options, path })));
} else {
syncDebounced(dispatch, getState, options);
// Don't do anything if the browser is not online. When it gets back
// from an offline state, a new `sync`action will be triggered then.
if (getState().base.get('online')) {
// If the user hits the 'sync' button, no matter if there's a sync
// in progress or if the sync 'should' be debounced, listen to the
// user and start a sync.
if (options.forceAction === 'manual') {
console.log('forcing sync');
const files = getState().org.present.get('files');
// sync all files on manual sync
files.keySeq().forEach((path) => dispatch(doSync({ ...options, path })));
} else {
syncDebounced(dispatch, getState, options);
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ exports[`Render all views Org Functionality Renders everything starting from an
class="action-drawer-container nice-scroll"
>
<button
class="btn btn--circle action-drawer__btn fas fa-lg fa-cloud action-drawer__btn--with-sub-icon"
class="btn btn--circle action-drawer__btn fas fa-lg fa-cloud action-drawer__btn--with-sub-icon btn--disabled"
style="opacity: 1;"
title="Sync changes"
>
Expand Down
4 changes: 3 additions & 1 deletion src/components/OrgFile/components/ActionDrawer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const ActionDrawer = ({
selectedTableCellId,
inEditMode,
isLoading,
online,
shouldDisableSyncButtons,
}) => {
const [isDisplayingArrowButtons, setIsDisplayingArrowButtons] = useState(false);
Expand Down Expand Up @@ -354,7 +355,7 @@ const ActionDrawer = ({
iconName="cloud"
subIconName="sync-alt"
shouldSpinSubIcon={isLoading}
isDisabled={shouldDisableSyncButtons}
isDisabled={shouldDisableSyncButtons || !online}
onClick={handleSync}
style={{
opacity:
Expand Down Expand Up @@ -400,6 +401,7 @@ const mapStateToProps = (state) => {
captureTemplates: state.capture.get('captureTemplates', List()),
path,
isLoading: !state.base.get('isLoading').isEmpty(),
online: state.base.get('online'),
};
};

Expand Down
109 changes: 109 additions & 0 deletions src/lib/initial_setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { debounce } from 'lodash';

import { setPath, sync } from '../actions/org';
import { setIsOnline } from '../actions/base';
import { STATIC_FILE_PREFIX } from './org_utils';

// When offline, don't sync and don't enable sync button. When online,
// enable sync button. When newly online, force sync of all things
// possibly dirty.
export function listenToNetworkConnectionEvents(store) {
// Is the user online when starting up the application?
store.dispatch(setIsOnline(navigator.onLine));

window.onoffline = function () {
store.dispatch(setIsOnline(false));
};

window.ononline = function () {
store.dispatch(setIsOnline(true));
// Add grace period, because browsers might get the event `onLine`
// too soon to actually do network requests.
setTimeout(() => {
sync({ shouldSuppressMessages: true, forceAction: 'manual' })(store.dispatch, store.getState);
}, 2000);
};
}

// Generally, opening files and other routing is done with the
// `react-router-dom` package (i.e. `<Link/ > tags). However, organice
// also should react to browser buttons.
export function listenToBrowserButtons(store) {
const path = store.getState().org.present.get('path');

const urlPathMatch = window.location.href.match(/.*file(\/.*)/);

let urlPath = path;

// The user has gone 'back' or 'forward' and is showing a 'file'
// URL.
if (urlPathMatch) {
urlPath = urlPathMatch[1];
}

window.onpopstate = function () {
// If the current 'path' in Redux is not the same as the one in
// the URL.
if (path !== urlPath) {
store.dispatch(setPath(path));
}
};
}

// BEGIN: Sync on Becoming Visible

const dispatchSync = (store) => {
if (
store.getState().syncBackend.get('client') &&
store.getState().base.get('shouldSyncOnBecomingVisibile')
) {
const path = store.getState().org.present.get('path');
let filesToLoadOnStartup = store
.getState()
.org.present.get('fileSettings')
.filter((setting) => setting.get('loadOnStartup'))
.map((setting) => setting.get('path'));
if (
path &&
!path.startsWith(STATIC_FILE_PREFIX) &&
!filesToLoadOnStartup.find((filepath) => path === filepath)
) {
filesToLoadOnStartup = filesToLoadOnStartup.push(path);
}
filesToLoadOnStartup.forEach((path) => {
sync({ path, shouldSuppressMessages: true })(store.dispatch, store.getState);
});
}
};

// The 'visibilitychange' event might get triggered in some browsers
// many times for one 'real' event of the browser becoming visible to
// the user. Debouncing through lodash ensures that it is called at
// maximum once every second.
const debouncedDispatchSync = debounce(dispatchSync, 1000);

// Dealing with vendor prefixes
function getHiddenProp() {
const prefixes = ['webkit', 'moz', 'ms', 'o'];

// if 'hidden' is natively supported just return it
if ('hidden' in document) return 'hidden';

// otherwise loop over all the known prefixes until we find one
for (let i = 0; i < prefixes.length; i++) {
if (prefixes[i] + 'Hidden' in document) return prefixes[i] + 'Hidden';
}

// otherwise it's not supported
return null;
}

export function syncOnBecomingVisible(store) {
let visProp = getHiddenProp();
if (visProp) {
const evtname = visProp.replace(/[H|h]idden/, '') + 'visibilitychange';
document.addEventListener(evtname, () => debouncedDispatchSync(store));
}
}

// END: Sync on Becoming Visible
6 changes: 6 additions & 0 deletions src/reducers/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ const setIsLoading = (state, action) => {
}
};

const setIsOnline = (state, action) => {
return state.set('online', action.online);
};

const setAgendaTimeframe = (state, action) => state.set('agendaTimeframe', action.agendaTimeframe);

const setColorScheme = (state, action) => {
Expand Down Expand Up @@ -183,6 +187,8 @@ export default (state = Map(), action) => {
return closePopup(state, action);
case 'SET_IS_LOADING':
return setIsLoading(state, action);
case 'SET_IS_ONLINE':
return setIsOnline(state, action);
case 'SET_AGENDA_TIMEFRAME':
return setAgendaTimeframe(state, action);
default:
Expand Down
3 changes: 1 addition & 2 deletions src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import liveSync from './middleware/live_sync';
import toggleColorScheme from './middleware/toggle_color_scheme';
import syncOnBecomingVisible from './middleware/sync_on_becoming_visible';
import rootReducer from './reducers';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
Expand All @@ -11,5 +10,5 @@ export default (initialState) =>
createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(thunk, liveSync, toggleColorScheme, syncOnBecomingVisible))
composeEnhancers(applyMiddleware(thunk, liveSync, toggleColorScheme))
);