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

[ShapeUp] Search and Discovery 1.0 #3032

Merged
merged 11 commits into from
Mar 26, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ interface ResultHeaderProps {
isOpenFilterModal: boolean;
}
const SearchResultsHeader = ({
total,
phrase,
sort,
handleSort,
isFiltersOpened,
isOpenFilterModal,
phrase,
sort,
toggleFilter,
toggleFilterModal,
isOpenFilterModal,
total,
}: ResultHeaderProps) => {
const totalText = total && total > 1 ? "results" : "result";
const title = phrase ? (
Expand Down
8 changes: 4 additions & 4 deletions client/src/features/kgSearch/KgSearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,14 @@ function SearchPage({ userName, isLoggedUser, model }: SearchPageProps) {
}
>
<SearchResultsHeader
total={error ? 0 : data?.total}
handleSort={(value: SortingOptions) => setSort(value)}
isFiltersOpened={isOpenFilter}
isOpenFilterModal={isOpenFilterModal}
phrase={decodeURIComponent(phrase)}
sort={sort}
isFiltersOpened={isOpenFilter}
toggleFilter={() => setIsOpenFilter(!isOpenFilter)}
toggleFilterModal={setIsOpenFilterModal}
isOpenFilterModal={isOpenFilterModal}
handleSort={(value: SortingOptions) => setSort(value)}
total={error ? 0 : data?.total}
/>
</Col>
{filter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ const injectedApi = api.injectEndpoints({
const enhancedApi = injectedApi.enhanceEndpoints({
addTagTypes: ["Project", "Members"],
endpoints: {
postProjects: {
invalidatesTags: ["Project"],
},
deleteProjectsByProjectId: {
invalidatesTags: ["Project"],
},
Expand All @@ -72,6 +69,9 @@ const enhancedApi = injectedApi.enhanceEndpoints({
patchProjectsByProjectIdMembers: {
invalidatesTags: ["Members"],
},
postProjects: {
invalidatesTags: ["Project"],
},
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ export default function ProjectRepositoryFormField({
pattern: /^(http|https):\/\/[^ "]+$/,
}}
/>
<Button className="ms-1" onClick={onDelete}>
<Button
className={cx("btn-outline-rk-green", "ms-1")}
onClick={onDelete}
>
X
</Button>
</div>
Expand Down
18 changes: 13 additions & 5 deletions client/src/features/projectsV2/new/ProjectV2FormSubmitGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Button } from "reactstrap";

import type { NewProjectV2State } from "./projectV2New.slice";
import { setCurrentStep } from "./projectV2New.slice";
import { ArrowLeft, ArrowRight } from "react-bootstrap-icons";

interface ProjectV2NewFormProps {
currentStep: NewProjectV2State["currentStep"];
Expand All @@ -42,23 +43,30 @@ export default function ProjectFormSubmitGroup({
return (
<div className={cx("d-flex", "justify-content-between")}>
<Button
className={cx(currentStep > 0 ? "visible" : "invisible")}
className={cx(
"btn-outline-rk-green",
currentStep > 0 ? "visible" : "invisible"
)}
onClick={previousStep}
>
Back
<ArrowLeft /> Back
</Button>
<div>
{currentStep === 0 && (
<Button className="me-1" type="submit">
Set Visibility
Set Visibility <ArrowRight />
</Button>
)}
{currentStep === 1 && (
<Button className="me-1" type="submit">
Add repositories
Add repositories <ArrowRight />
</Button>
)}
{currentStep === 2 && (
<Button type="submit">
Review <ArrowRight />
</Button>
)}
{currentStep === 2 && <Button type="submit">Review</Button>}
{currentStep === 3 && <Button type="submit">Create</Button>}
</div>
</div>
Expand Down
9 changes: 8 additions & 1 deletion client/src/features/projectsV2/new/ProjectV2New.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { projectWasCreated, setCurrentStep } from "./projectV2New.slice";
import ProjectFormSubmitGroup from "./ProjectV2FormSubmitGroup";
import ProjectV2NewForm from "./ProjectV2NewForm";
import WipBadge from "../shared/WipBadge";
import { ArrowLeft } from "react-bootstrap-icons";

function projectToProjectPost(
project: NewProjectV2State["project"]
Expand Down Expand Up @@ -145,7 +146,9 @@ function ProjectV2BeingCreated({
<div>
<p>Something went wrong.</p>
<div className={cx("d-flex", "justify-content-between")}>
<Button onClick={previousStep}>Back</Button>
<Button onClick={previousStep}>
<ArrowLeft /> Back
</Button>
</div>
</div>
);
Expand Down Expand Up @@ -202,6 +205,10 @@ function ProjectV2NewReviewCreateStep({

export default function ProjectV2New() {
const user = useLegacySelector((state) => state.stateModel.user);
const dispatch = useDispatch();
useEffect(() => {
dispatch(setCurrentStep(0));
}, [dispatch]);
const { currentStep } = useAppSelector((state) => state.newProjectV2);
if (!user.logged) {
return <h2>Please log in to create a project.</h2>;
Expand Down
15 changes: 6 additions & 9 deletions client/src/features/projectsV2/new/ProjectV2NewForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
setCurrentStep,
setMetadata,
} from "./projectV2New.slice";
import { PlusLg } from "react-bootstrap-icons";

interface ProjectV2NewFormProps {
currentStep: NewProjectV2State["currentStep"];
Expand Down Expand Up @@ -194,20 +195,13 @@ function ProjectV2NewRepositoryStepForm({
);
return (
<>
<div className={cx("d-flex", "justify-content-between")}>
<h4>Add repositories</h4>
<div>
<Button data-cy="project-add-repository" onClick={onAppend}>
Add
</Button>
</div>
</div>
<h4>Add Repositories</h4>
<Form
className="form-rk-green"
noValidate
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-3">
<div className="mb-5">
{fields.map((f, i) => {
return (
<div key={f.id}>
Expand All @@ -222,6 +216,9 @@ function ProjectV2NewRepositoryStepForm({
</div>
);
})}
<Button data-cy="project-add-repository" onClick={onAppend}>
<PlusLg /> repository
</Button>
</div>
<ProjectFormSubmitGroup currentStep={currentStep} />
</Form>
Expand Down
7 changes: 6 additions & 1 deletion client/src/features/rootV2/NavbarV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ export default function NavbarV2() {
<span className="me-1">Renku 2.0</span>
<WipBadge />
</div>
<Nav className="navbar-nav">
<Nav className={cx("navbar-nav", "flex-row", "gap-4")}>
<NavItem>
<RenkuNavLinkV2 end to="search" title="Search">
Search
</RenkuNavLinkV2>
</NavItem>
<NavItem>
<RenkuNavLinkV2 end to="projects" title="Projects">
Projects
Expand Down
9 changes: 9 additions & 0 deletions client/src/features/rootV2/RootV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import LazyProjectV2Show from "../projectsV2/LazyProjectV2Show";
import LazySessionStartPage from "../sessionsV2/LazySessionStartPage";
import LazyShowSessionPage from "../sessionsV2/LazyShowSessionPage";
import NavbarV2 from "./NavbarV2";
import LazySearchV2 from "../searchV2/LazySearchV2";

export default function RootV2() {
const navigate = useNavigate();
Expand Down Expand Up @@ -68,6 +69,14 @@ export default function RootV2() {
// </ContainerWrap>
}
/>
<Route
path="search*"
element={
<ContainerWrap>
<LazySearchV2 />
</ContainerWrap>
}
/>
<Route
path="*"
element={
Expand Down
29 changes: 29 additions & 0 deletions client/src/features/searchV2/LazySearchV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
import { Suspense, lazy } from "react";
import PageLoader from "../../components/PageLoader";

const SearchV2 = lazy(() => import("./SearchV2"));

export default function LazySearchV2() {
return (
<Suspense fallback={<PageLoader />}>
<SearchV2 />
</Suspense>
);
}
53 changes: 53 additions & 0 deletions client/src/features/searchV2/SearchV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
import { Col, Row } from "reactstrap";

import SearchV2Bar from "./components/SearchV2Bar";
import SearchV2Filters from "./components/SearchV2Filters";
import SearchV2Header from "./components/SearchV2Header";
import SearchV2Results from "./components/SearchV2Results";

export default function SearchV2() {
return (
<>
<Row className="mb-3">
<Col>
<h2>Search v2</h2>
</Col>
</Row>
<Row className="mb-3">
<Col>
<SearchV2Bar />
</Col>
</Row>
<Row className="mb-3">
<Col>
<SearchV2Header />
</Col>
</Row>
<Row className="mb-3">
<Col xs={12} sm={4} lg={3}>
<SearchV2Filters />
</Col>
<Col xs={12} sm={8} lg={9}>
<SearchV2Results />
</Col>
</Row>
</>
);
}
83 changes: 83 additions & 0 deletions client/src/features/searchV2/components/SearchV2Bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
import cx from "classnames";
import { useEffect, useRef } from "react";
import { useDispatch } from "react-redux";
import { Button, InputGroup } from "reactstrap";

import { setQuery } from "../searchV2.slice";
import useAppSelector from "../../../utils/customHooks/useAppSelector.hook";
import useStartNewSearch from "../useStartSearch.hook";

export default function SearchV2Bar() {
const dispatch = useDispatch();
const searchState = useAppSelector((state) => state.searchV2);
const { startNewSearch } = useStartNewSearch();

// focus search input when loading the component
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);

// handle pressing Enter to search
// ? We could use react-hotkeys-hook if we wish to handle Enter also outside the input
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
startNewSearch();
}
};

// basic autocomplete for searched values, without duplicates
const previousSearchEntries = Array.from(
new Set(searchState.search.history.map((entry) => entry.query))
).map((value) => <option key={value} value={value} />);

return (
<InputGroup data-cy="search-bar">
<input
autoComplete="renku-search"
className={cx("form-control", "rounded-0", "rounded-start")}
data-cy="search-input"
id="search-input"
list="previous-searches"
onChange={(e) => dispatch(setQuery(e.target.value))}
onKeyDown={handleKeyDown}
placeholder="Search..."
ref={inputRef}
tabIndex={-1}
type="text"
value={searchState.search.query}
/>
{previousSearchEntries.length > 0 && (
<datalist id="previous-searches">{previousSearchEntries}</datalist>
)}
<Button
className="rounded-end"
color="secondary"
data-cy="search-button"
id="search-button"
onClick={startNewSearch}
>
Search
</Button>
</InputGroup>
);
}
Loading
Loading