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

Small UI/UX/DX things #318

Merged
merged 10 commits into from
Jun 27, 2024
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 pephub/routers/api/v1/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ async def remove_from_stars(
return JSONResponse(
content={
"message": "PEP was removed from favorites.",
"namespace": namespace,
"registry": f"{project.namespace}/{project.name}:{project.tag}",
},
status_code=202,
Expand Down
16 changes: 14 additions & 2 deletions web/src/api/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ export interface StarsResponse {
results: ProjectAnnotation[];
}

interface AddStarResponse {
namespace: string;
registry_path: string;
message: string;
}

interface RemoveStarResponse {
message: string;
registry: string;
namespace: string;
}

export const getNamespaceInfo = (namespace: string, token: string | null = null) => {
const url = `${API_BASE}/namespaces/${namespace}/`; // note the trailing slash
if (!token) {
Expand Down Expand Up @@ -270,7 +282,7 @@ export const starProject = (
) => {
const url = `${API_BASE}/namespaces/${namespace}/stars`;
return axios
.post(
.post<AddStarResponse>(
url,
{
namespace: star_namespace,
Expand Down Expand Up @@ -298,7 +310,7 @@ export const removeStar = (
) => {
const url = `${API_BASE}/namespaces/${namespace}/stars`;
return axios
.delete(url, {
.delete<RemoveStarResponse>(url, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/forms/blank-project-form.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ErrorMessage } from '@hookform/error-message';
import { FC } from 'react';
import { Tab, Tabs } from 'react-bootstrap';
import { Controller, useForm } from 'react-hook-form';

Expand Down Expand Up @@ -125,6 +124,7 @@ sample_table: samples.csv
Private
</label>
</div>

<span className="fs-4 d-flex align-items-center">
<select
id="blank-namespace-select"
Expand Down Expand Up @@ -200,7 +200,7 @@ sample_table: samples.csv
</div>
<Tabs defaultActiveKey="samples" id="blank-project-tabs" className="mt-3">
<Tab eventKey="samples" title="Samples">
<div className="p-2 border border-top-1">
<div className="p-2 -1">
<SampleTable
height={300}
data={sampleTable}
Expand All @@ -211,7 +211,7 @@ sample_table: samples.csv
</div>
</Tab>
<Tab eventKey="config" title="Config">
<div className="p-1 border border-top-0">
<div className="p-1 -0">
<ProjectConfigEditor
value={configYAML}
setValue={(data) => {
Expand Down
32 changes: 20 additions & 12 deletions web/src/components/layout/project-data-nav.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import { FC } from 'react';

import { ProjectViewAnnotation } from '../../../types';
import { ProjectViewsResponse } from '../../api/project';
import { useProjectPage } from '../../contexts/project-page-context';
import { ViewSelector } from '../project/view-selector';

type PageView = 'samples' | 'subsamples' | 'config';

interface NavProps {
pageView: string;
setPageView: (view: PageView) => void;
samplesIsDirty: boolean;
subsamplesIsDirty: boolean;
configIsDirty: boolean;
projectViewIsLoading: boolean;
projectViews: ProjectViewsResponse | undefined;
projectViews: ProjectViewAnnotation[] | undefined;
projectView: string | undefined;
setProjectView: (view: string | undefined) => void;
}

interface ViewButtonProps {
view: PageView;
setPageView: (view: PageView) => void;
icon: string;
text: string;
isDirty: boolean;
Expand All @@ -43,14 +40,24 @@ const ViewButton: FC<ViewButtonProps> = ({ view, setPageView, icon, text, isDirt
<span className="text-xs">
<i className="bi bi-circle-fill ms-1 text-transparent"></i>
</span>
)}
</button>
</div>
);
<i className={icon}></i>
{text}
{isDirty ? (
<span className="text-xs">
<i className="bi bi-circle-fill ms-1 text-primary-light"></i>
</span>
) : (
// spacer
<span className="text-xs">
<i className="bi bi-circle-fill ms-1 text-transparent"></i>
</span>
)}
</button>
</div>
);
};

export const ProjectDataNav: FC<NavProps> = ({
pageView,
setPageView,
samplesIsDirty,
subsamplesIsDirty,
configIsDirty,
Expand All @@ -59,6 +66,8 @@ export const ProjectDataNav: FC<NavProps> = ({
projectView,
setProjectView,
}) => {
const { pageView } = useProjectPage();

return (
<div className="h-100 w-100 d-flex flex-row align-items-center">
<div
Expand Down Expand Up @@ -93,7 +102,6 @@ export const ProjectDataNav: FC<NavProps> = ({
>
<ViewButton
view="subsamples"
setPageView={setPageView}
icon="bi bi-grid-3x3-gap-fill me-2"
text="Subsamples"
isDirty={subsamplesIsDirty}
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/pop/pop-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ interface Props {

export const PopCard: FC<Props> = ({ project, currentPeps, parentName, parentNamespace, parentTag }) => {
const { user } = useSession();
const { data: stars } = useNamespaceStars(user?.login, {}, true); // only fetch stars if the namespace is the user's
const { starsQuery } = useNamespaceStars(user?.login || '/', {}, true); // only fetch stars if the namespace is the user's
const stars = starsQuery.data;

const [showForkPEPModal, setShowForkPEPModal] = useState(false);
const [showRemovePEPModal, setShowRemovePEPModal] = useState(false);
Expand Down
22 changes: 15 additions & 7 deletions web/src/components/project/project-card-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import toast from 'react-hot-toast';
import { ProjectAnnotation } from '../../../types';
import { useAddStar } from '../../hooks/mutations/useAddStar';
import { useRemoveStar } from '../../hooks/mutations/useRemoveStar';
import { useNamespaceStars } from '../../hooks/queries/useNamespaceStars';
import { useSession } from '../../hooks/useSession';
import { copyToClipboard } from '../../utils/etc';
import { LoadingSpinner } from '../spinners/loading-spinner';
Expand All @@ -22,22 +23,29 @@ export const ProjectCardDropdown: FC<Props> = (props) => {
const { project, isStarred, copied, setCopied, setShowDeletePEPModal, setShowForkPEPModal } = props;
const { user } = useSession();

const starAddMutation = useAddStar(user?.login || '', project.namespace, project.name, project.tag);
const starRemoveMutation = useRemoveStar(user?.login || '', project.namespace, project.name, project.tag);
const { addStarMutation, removeStarMutation } = useNamespaceStars(user?.login || '/', {}, true);

return (
<Dropdown as={ButtonGroup}>
<Button
disabled={starAddMutation.isPending || starRemoveMutation.isPending}
disabled={addStarMutation.isPending || removeStarMutation.isPending}
variant="outline-dark"
size="sm"
onClick={() => {
if (!user) {
toast.error('You must be logged in to star a project!');
} else if (isStarred) {
starRemoveMutation.mutate();
removeStarMutation.mutate({
namespaceToRemove: project.namespace,
projectNameToRemove: project.name,
projectTagToRemove: project.tag,
});
} else {
starAddMutation.mutate();
addStarMutation.mutate({
namespaceToStar: project.namespace,
projectNameToStar: project.name,
projectTagToStar: project.tag,
});
}
}}
>
Expand All @@ -46,7 +54,7 @@ export const ProjectCardDropdown: FC<Props> = (props) => {
<div className="d-flex align-items-center">
<i className="text-primary bi bi-star-fill me-1"></i>
<span className="text-primary">
{starRemoveMutation.isPending ? (
{removeStarMutation.isPending ? (
<Fragment>
{copied ? 'Copied!' : 'Star'}
<LoadingSpinner className="w-4 h-4 spin ms-1 mb-tiny fill-secondary" />
Expand All @@ -62,7 +70,7 @@ export const ProjectCardDropdown: FC<Props> = (props) => {
<div className="d-flex align-items-center">
<i className="bi bi-star me-1"></i>
<span>
{starAddMutation.isPending ? (
{addStarMutation.isPending ? (
<Fragment>
{copied ? 'Copied!' : 'Star'}
<LoadingSpinner className="w-4 h-4 spin ms-1 mb-tiny fill-secondary" />
Expand Down
4 changes: 3 additions & 1 deletion web/src/components/project/project-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ interface Props {

export const ProjectCard: FC<Props> = ({ project }) => {
const { user } = useSession();
const { data: stars } = useNamespaceStars(user?.login, {}, true); // only fetch stars if the namespace is the user's

const { starsQuery } = useNamespaceStars(user?.login || '/', {}, true); // only fetch stars if the namespace is the user's
const stars = starsQuery.data;

// state
const [showDeletePEPModal, setShowDeletePEPModal] = useState(false);
Expand Down
40 changes: 40 additions & 0 deletions web/src/components/project/project-info-footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Fragment } from 'react';

import { useProjectPage } from '../../contexts/project-page-context';
import { dateStringToDateTime } from '../../utils/dates';

export const ProjectInfoFooter = () => {
const { projectAnnotationQuery } = useProjectPage();

const projectInfo = projectAnnotationQuery.data;

return (
<div className="px-4">
<div className="d-flex flex-row align-items-center text-muted mt-1">
<small className="d-flex flex-row align-items-center justify-content-between w-100">
<span className="me-3">
<i className="bi bi-calendar3"></i>
<span className="mx-1">Created:</span>
<span id="project-submission-date">{dateStringToDateTime(projectInfo?.submission_date || '')}</span>
<i className="ms-4 bi bi-calendar3"></i>
<span className="mx-1">Updated:</span>
<span id="project-update-date">{dateStringToDateTime(projectInfo?.last_update_date || '')}</span>
</span>
<span className="">
{projectInfo?.forked_from && (
<span className="me-2 p-1 border rounded fw-bold">
<Fragment>
<i className="bi bi-bezier2"></i>
<span className="ms-1">Forked from</span>
<a className="text-decoration-none ms-1" href={`/${projectInfo?.forked_from.replace(':', '?tag=')}`}>
{projectInfo?.forked_from}
</a>
</Fragment>
</span>
)}
</span>
</small>
</div>
</div>
);
};
55 changes: 55 additions & 0 deletions web/src/components/project/project-page-description.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Fragment, useRef, useState } from 'react';

import { useProjectPage } from '../../contexts/project-page-context';
import { Markdown } from '../markdown/render';

const MAX_DESC_HEIGHT = 200;

export const ProjectDescription = () => {
const { projectAnnotationQuery } = useProjectPage();

const projectDescriptionRef = useRef<HTMLDivElement>(null);
const showMoreButton = projectDescriptionRef.current?.clientHeight! >= MAX_DESC_HEIGHT;
const [showMoreDescription, setShowMoreDescription] = useState(false);

const projectInfo = projectAnnotationQuery.data;

return (
<Fragment>
<div className="d-flex flex-row align-items-center justify-content-between px-4 w-100 border-bottom">
<div ref={projectDescriptionRef} className="w-100" style={{ maxHeight: MAX_DESC_HEIGHT, overflow: 'hidden' }}>
<Markdown>{projectInfo?.description || 'No description'}</Markdown>
</div>
</div>
{showMoreButton && (
<div className="d-flex flex-row justify-content-center mb-2 translate-y-1-up">
{showMoreDescription ? (
<button
className="btn btn-sm btn-dark rounded-pill"
onClick={() => {
projectDescriptionRef.current?.style.setProperty('max-height', `${MAX_DESC_HEIGHT}px`);
projectDescriptionRef.current?.style.setProperty('overflow', 'hidden');
setShowMoreDescription(false);
}}
>
<i className="bi bi-arrow-up" />
Less
</button>
) : (
<button
className="btn btn-sm btn-dark rounded-pill"
onClick={() => {
projectDescriptionRef.current?.style.removeProperty('max-height');
projectDescriptionRef.current?.style.removeProperty('overflow');
setShowMoreDescription(true);
}}
>
<i className="bi bi-arrow-down" />
More
</button>
)}
</div>
)}
</Fragment>
);
};
Loading
Loading