2021-06-23 16:40:48 +00:00
|
|
|
import { useCallback, useMemo, useEffect, useReducer, Reducer } from 'react';
|
2021-06-15 04:21:06 +00:00
|
|
|
import { useSelector } from 'react-redux';
|
|
|
|
import { isLoggedIn } from '../../account/session';
|
|
|
|
import { Project } from '../../sync/types';
|
|
|
|
import { VCS } from '../../sync/vcs/vcs';
|
|
|
|
import { pullProject } from '../../sync/vcs/pull-project';
|
|
|
|
import { showAlert } from '../components/modals';
|
|
|
|
import { selectActiveSpace, selectAllWorkspaces } from '../redux/selectors';
|
2021-06-23 16:40:48 +00:00
|
|
|
import { useSafeReducerDispatch } from './use-safe-reducer-dispatch';
|
|
|
|
|
|
|
|
interface State {
|
|
|
|
loading: boolean;
|
|
|
|
localProjects: Project[];
|
|
|
|
remoteProjects: Project[];
|
|
|
|
pullingProjects: Record<string, boolean>;
|
|
|
|
}
|
|
|
|
|
|
|
|
const initialState: State = {
|
|
|
|
loading: false,
|
|
|
|
localProjects: [],
|
|
|
|
remoteProjects: [],
|
|
|
|
pullingProjects: {},
|
|
|
|
};
|
|
|
|
|
|
|
|
type Action =
|
|
|
|
| { type: 'loadProjects' }
|
|
|
|
| { type: 'saveProjects', local: State['localProjects'], remote: State['remoteProjects']}
|
|
|
|
| { type: 'startPullingProject', projectId: string }
|
|
|
|
| { type: 'stopPullingProject', projectId: string }
|
|
|
|
|
|
|
|
const reducer: Reducer<State, Action> = (prevState, action) => {
|
|
|
|
switch (action.type) {
|
|
|
|
case 'loadProjects':
|
|
|
|
return { ...prevState, loading: true };
|
|
|
|
case 'saveProjects':
|
|
|
|
return { ...prevState, localProjects: action.local, remoteProjects: action.remote, loading: false };
|
|
|
|
case 'startPullingProject':
|
|
|
|
return { ...prevState, pullingProjects: { ...prevState.pullingProjects, [action.projectId]: true } };
|
|
|
|
case 'stopPullingProject':
|
|
|
|
return { ...prevState, pullingProjects: { ...prevState.pullingProjects, [action.projectId]: false } };
|
|
|
|
default:
|
|
|
|
return prevState;
|
|
|
|
}
|
|
|
|
};
|
2021-06-15 04:21:06 +00:00
|
|
|
|
|
|
|
export const useRemoteWorkspaces = (vcs?: VCS) => {
|
|
|
|
// Fetch from redux
|
|
|
|
const workspaces = useSelector(selectAllWorkspaces);
|
|
|
|
const activeSpace = useSelector(selectActiveSpace);
|
|
|
|
const spaceRemoteId = activeSpace?.remoteId || undefined;
|
|
|
|
|
|
|
|
// Local state
|
2021-06-23 16:40:48 +00:00
|
|
|
const [{ loading, localProjects, remoteProjects, pullingProjects }, _dispatch] = useReducer(reducer, initialState);
|
|
|
|
const dispatch = useSafeReducerDispatch(_dispatch);
|
2021-06-15 04:21:06 +00:00
|
|
|
|
|
|
|
// Refresh remote spaces
|
|
|
|
const refresh = useCallback(async () => {
|
|
|
|
if (!vcs || !isLoggedIn()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-23 16:40:48 +00:00
|
|
|
dispatch({ type: 'loadProjects' });
|
2021-06-15 04:21:06 +00:00
|
|
|
const remote = await vcs.remoteProjects(spaceRemoteId);
|
|
|
|
const local = await vcs.localProjects();
|
2021-06-23 16:40:48 +00:00
|
|
|
dispatch({ type: 'saveProjects', local, remote });
|
|
|
|
}, [dispatch, spaceRemoteId, vcs]);
|
2021-06-15 04:21:06 +00:00
|
|
|
|
|
|
|
// Find remote spaces that haven't been pulled
|
|
|
|
const missingProjects = useMemo(() => remoteProjects.filter(({ id, rootDocumentId }) => {
|
|
|
|
const localProjectExists = localProjects.find(p => p.id === id);
|
|
|
|
const workspaceExists = workspaces.find(w => w._id === rootDocumentId);
|
|
|
|
// Mark as missing if:
|
|
|
|
// - the project doesn't yet exists locally
|
|
|
|
// - the project exists locally but somehow the workspace doesn't anymore
|
|
|
|
return !(workspaceExists && localProjectExists);
|
|
|
|
}), [localProjects, remoteProjects, workspaces]);
|
|
|
|
|
|
|
|
// Pull a remote space
|
|
|
|
const pull = useCallback(async (project: Project) => {
|
|
|
|
if (!vcs) {
|
|
|
|
throw new Error('VCS is not defined');
|
|
|
|
}
|
|
|
|
|
2021-06-23 16:40:48 +00:00
|
|
|
dispatch({ type: 'startPullingProject', projectId: project.id });
|
2021-06-15 04:21:06 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
// Clone old VCS so we don't mess anything up while working on other projects
|
|
|
|
const newVCS = vcs.newInstance();
|
|
|
|
// Remove all projects for workspace first
|
|
|
|
await newVCS.removeProjectsForRoot(project.rootDocumentId);
|
|
|
|
|
2021-06-25 19:32:29 +00:00
|
|
|
await pullProject({ vcs: newVCS, project, space: activeSpace });
|
2021-06-15 04:21:06 +00:00
|
|
|
|
|
|
|
await refresh();
|
|
|
|
} catch (err) {
|
|
|
|
showAlert({
|
|
|
|
title: 'Pull Error',
|
|
|
|
message: `Failed to pull workspace. ${err.message}`,
|
|
|
|
});
|
|
|
|
} finally {
|
2021-06-23 16:40:48 +00:00
|
|
|
dispatch({ type: 'stopPullingProject', projectId: project.id });
|
2021-06-15 04:21:06 +00:00
|
|
|
}
|
2021-06-25 19:32:29 +00:00
|
|
|
}, [vcs, refresh, activeSpace, dispatch]);
|
2021-06-15 04:21:06 +00:00
|
|
|
|
|
|
|
// If the refresh callback changes, refresh
|
|
|
|
useEffect(() => {
|
|
|
|
(async () => { await refresh(); })();
|
|
|
|
}, [refresh]);
|
|
|
|
|
|
|
|
return {
|
|
|
|
loading,
|
|
|
|
missingProjects,
|
|
|
|
pullingProjects,
|
|
|
|
refresh,
|
|
|
|
pull,
|
|
|
|
};
|
|
|
|
};
|