Skip to content

Commit

Permalink
Project creation (#52)
Browse files Browse the repository at this point in the history
* Restore 'new project' menu to navigation bar

* Create new projects based on JSONized exports of the starter templates

* Permit the namespace value of "@user" in the new project modal

-- previously this would be forced to be blank. I don't entirely know why, but for now we want to permit it again.
-- once github integration is complete, this special case can be removed entirely as we rely on github usernames instead.

* Cleanup: Stop the failing 'commitActions' API call

* Cleanup: Stop the failing 'searchResourceProjects' API call

* Cleanup: prevent the analytics 'registerEvents' call

* Remove 10 item limit from 'recent projects' list

-- Since this is our main project browser for the moment, having things fall off the end of the list is no good.

* Further cleanup of SearchableIndex migration shim
  • Loading branch information
judeallred authored Oct 24, 2023
1 parent 09d1f25 commit 9bbae6c
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export const HashCoreHeaderMenuFiles: FC<HashCoreHeaderMenuFilesProps> = memo(
</ul>
</li>
)}
{/* <li className="HashCoreHeaderMenu-submenu-item">
<li className="HashCoreHeaderMenu-submenu-item">
<LabeledInputRadio
group="HashCoreHeaderMenu-submenu"
label="New project"
Expand All @@ -182,7 +182,7 @@ export const HashCoreHeaderMenuFiles: FC<HashCoreHeaderMenuFilesProps> = memo(
From starter template
</Link>
</ul>
</li> */}
</li>
<li>
<hr />
</li>
Expand All @@ -201,7 +201,6 @@ export const HashCoreHeaderMenuFiles: FC<HashCoreHeaderMenuFilesProps> = memo(
<ul>
{[...userProjects]
.sort(descByUpdatedAt)
.slice(0, 10)
.map(toListItem("User"))}
{userProfileUrl ? (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,16 @@
import { useEffect, useReducer, useRef } from "react";
import { useDispatch, useStore } from "react-redux";
import { Subject, combineLatest, merge } from "rxjs";
import {
debounceTime,
distinctUntilChanged,
filter,
map,
skip,
take,
withLatestFrom,
} from "rxjs/operators";
import { useReducer } from "react";

import type { ResourceProject } from "../../../../features/project/types";
import { Scope, selectScope } from "../../../../features/scopes";
import { fromStore } from "../../../../util/fromStore";
import { projectChangeObservable } from "../../../../features/project/observables";
import { searchResourceProjects } from "../../../../util/api/queries/searchResourceProjects";
import {
selectLatestReleaseTag,
selectProjectLoaded,
} from "../../../../features/project/selectors";
import { trackEvent } from "../../../../features/analytics";

export const useSearchIndex = (): {
loading: boolean;
results: ResourceProject[];
onChange: (term: string) => void;
searchTerm: string;
} => {
const searchTermSubjectRef = useRef(new Subject<string>());
const appDispatch = useDispatch();
const store = useStore();
// const searchTermSubjectRef = useRef(new Subject<string>());
// const appDispatch = useDispatch();
// const store = useStore();

const [{ loading, results, searchTerm }, dispatch] = useReducer(
(
Expand Down Expand Up @@ -62,83 +42,84 @@ export const useSearchIndex = (): {
{ loading: true, results: [], searchTerm: "" }
);

useEffect(() => {
const search = async (searchTerm: string, signal: AbortSignal) => {
try {
dispatch({ type: "BEGIN_SEARCH" });

const results = await searchResourceProjects(searchTerm, signal);

// Search is triggered on page load - we don't want to track those as events
if (searchTerm) {
appDispatch(
trackEvent({ action: "Index Search: Core", label: searchTerm })
);
}

if (signal.aborted) {
return;
}

dispatch({ type: "FINISHED_SEARCHING", payload: results });
} catch (err) {
if (err.name !== "AbortError") {
console.error("Could not fetch resources", err);

dispatch({ type: "ERROR" });
}
}
};

let controller: AbortController | null = null;

const storeObs = fromStore(store);
const subscription = combineLatest([
merge(
searchTermSubjectRef.current.pipe(skip(1), debounceTime(500)),
searchTermSubjectRef.current.pipe(take(1)),
projectChangeObservable(store).pipe(
withLatestFrom(searchTermSubjectRef.current),
map((pair) => pair[1] ?? "")
),
storeObs.pipe(
filter(selectProjectLoaded),
map(selectLatestReleaseTag),
distinctUntilChanged(),
withLatestFrom(searchTermSubjectRef.current),
map((pair) => pair[1] ?? "")
)
),
storeObs.pipe(
filter(selectProjectLoaded),
map(selectScope[Scope.save]),
distinctUntilChanged()
),
])
.pipe(debounceTime(0))
.subscribe(([searchTerm, canSave]) => {
controller?.abort();

if (canSave) {
controller = new AbortController();

search(searchTerm, controller.signal).catch((err) => {
if (err.name !== "AbortError") {
console.error(err);
}
});
}
});

return () => {
controller?.abort();
subscription.unsubscribe();
};
}, [appDispatch, store]);

useEffect(() => {
searchTermSubjectRef.current.next(searchTerm);
}, [searchTerm]);
// migration shim
// useEffect(() => {
// const search = async (searchTerm: string, signal: AbortSignal) => {
// try {
// dispatch({ type: "BEGIN_SEARCH" });

// const results = await searchResourceProjects(searchTerm, signal);

// // Search is triggered on page load - we don't want to track those as events
// if (searchTerm) {
// appDispatch(
// trackEvent({ action: "Index Search: Core", label: searchTerm })
// );
// }

// if (signal.aborted) {
// return;
// }

// dispatch({ type: "FINISHED_SEARCHING", payload: results });
// } catch (err) {
// if (err.name !== "AbortError") {
// console.error("Could not fetch resources", err);

// dispatch({ type: "ERROR" });
// }
// }
// };

// let controller: AbortController | null = null;

// const storeObs = fromStore(store);
// const subscription = combineLatest([
// merge(
// searchTermSubjectRef.current.pipe(skip(1), debounceTime(500)),
// searchTermSubjectRef.current.pipe(take(1)),
// projectChangeObservable(store).pipe(
// withLatestFrom(searchTermSubjectRef.current),
// map((pair) => pair[1] ?? "")
// ),
// storeObs.pipe(
// filter(selectProjectLoaded),
// map(selectLatestReleaseTag),
// distinctUntilChanged(),
// withLatestFrom(searchTermSubjectRef.current),
// map((pair) => pair[1] ?? "")
// )
// ),
// storeObs.pipe(
// filter(selectProjectLoaded),
// map(selectScope[Scope.save]),
// distinctUntilChanged()
// ),
// ])
// .pipe(debounceTime(0))
// .subscribe(([searchTerm, canSave]) => {
// controller?.abort();

// if (canSave) {
// controller = new AbortController();

// search(searchTerm, controller.signal).catch((err) => {
// if (err.name !== "AbortError") {
// console.error(err);
// }
// });
// }
// });

// return () => {
// controller?.abort();
// subscription.unsubscribe();
// };
// }, [appDispatch, store]);

// useEffect(() => {
// searchTermSubjectRef.current.next(searchTerm);
// }, [searchTerm]);

return {
onChange: (searchTerm: string) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { navigate } from "hookrouter";
import React, { FC, useEffect } from "react";
import { useDispatch } from "react-redux";
import { useModal } from "react-modal-hook";
import { navigate } from "hookrouter";
import { useDispatch } from "react-redux";

import { AppDispatch } from "../../../features/types";
import { ModalNewProject } from "../../Modal/NewProject/ModalNewProject";
import { setProjectWithMeta } from "../../../features/actions";
import { trackEvent } from "../../../features/analytics";
import { preparePartialSimulationProject } from "../../../features/project/utils";
import { Scope, useScopes } from "../../../features/scopes";
import { AppDispatch } from "../../../features/types";
import { addUserProject } from "../../../features/user/slice";
import { createNewSimulationProject } from "../../../util/api/queries/createNewSimulationProject";
import { forceLogIn } from "../../../features/user/utils";
import { preparePartialSimulationProject } from "../../../features/project/utils";
import { setProjectWithMeta } from "../../../features/actions";
import { templates } from "./templates/templates";
import { trackEvent } from "../../../features/analytics";
import { useSafeQueryParams } from "../../../hooks/useSafeQueryParams";
import { urlFromProject } from "../../../routes";
import { useFatalError } from "../../ErrorBoundary/ErrorBoundary";
import { ModalNewProject } from "../../Modal/NewProject/ModalNewProject";
import { useNavigateAway } from "./hooks";
import { useSafeQueryParams } from "../../../hooks/useSafeQueryParams";
import { createNewSimulationProjectFromTemplate } from "./templates/templates";

export const HashRouterEffectNewProject: FC<{ template?: string }> = ({
template = "empty",
Expand All @@ -30,22 +29,19 @@ export const HashRouterEffectNewProject: FC<{ template?: string }> = ({
);
const fatalError = useFatalError();

const actions = templates[template];
if (!actions) {
throw new Error(`Unrecognised template ${template}`);
}

const [showModal, hideModal] = useModal(
() => (
<ModalNewProject
onCancel={navigateAway}
onSubmit={async (values) => {
const project = await createNewSimulationProject(
//migration shim

const project = createNewSimulationProjectFromTemplate(
values.namespace,
values.path,
values.name,
values.visibility,
actions
template
);

dispatch(
Expand All @@ -55,18 +51,15 @@ export const HashRouterEffectNewProject: FC<{ template?: string }> = ({
})
);

if (!values.namespace) {
dispatch(addUserProject(preparePartialSimulationProject(project)));
}

dispatch(addUserProject(preparePartialSimulationProject(project)));
dispatch(setProjectWithMeta(project));
navigate(urlFromProject(project), false, {}, true);
}}
action="Create New Simulation"
defaultNamespace={namespace}
/>
),
[actions, dispatch, namespace, navigateAway]
[dispatch, namespace, navigateAway]
);

useEffect(() => {
Expand Down
Loading

0 comments on commit 9bbae6c

Please sign in to comment.