Skip to content

Commit

Permalink
wip: improved actions
Browse files Browse the repository at this point in the history
  • Loading branch information
leafty committed Jan 23, 2025
1 parent 5cbebaf commit f6b195f
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
top: 0;
margin-top: 1rem;
right: 0;
bottom: auto;
}
}
3 changes: 1 addition & 2 deletions client/src/features/dashboardV2/DashboardV2Sessions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,8 @@ function DashboardSession({ session }: DashboardSessionProps) {
</Row>
</Link>
{/* NOTE: The session actions button is visually placed within the link card, but its DOM tree is kept separate. */}
<div className={cx(styles.sessionButton, "position-absolute", "z-1")}>
<div className={cx(styles.sessionButton, "position-absolute")}>
<ActiveSessionButton
className="my-auto"
session={session}
showSessionUrl={showSessionUrl}
/>
Expand Down
17 changes: 17 additions & 0 deletions client/src/features/sessionsV2/SessionList/Actions.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/variables-dark";
@import "~bootstrap/scss/maps";
@import "~bootstrap/scss/mixins";

.actionsButton {
bottom: 0;
margin-bottom: 1rem;

@include media-breakpoint-up(md) {
top: 0;
margin-top: 1rem;
right: 0;
bottom: auto;
}
}
62 changes: 47 additions & 15 deletions client/src/features/sessionsV2/SessionList/SessionItemV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@
import cx from "classnames";
import { useCallback, useMemo } from "react";
import { CaretRightFill } from "react-bootstrap-icons";
import { ListGroupItem } from "reactstrap";
import { Link } from "react-router-dom-v5-compat";
import { Col, ListGroupItem, Row } from "reactstrap";

import useLocationHash from "../../../utils/customHooks/useLocationHash.hook";
import { useProject } from "../../ProjectPageV2/ProjectPageContainer/ProjectPageContainer";
import ActiveSessionButton from "../components/SessionButton/ActiveSessionButton";
import { SessionStatusV2Label } from "../components/SessionStatus/SessionStatus";
import { getShowSessionUrlByProject } from "../SessionsV2";
import type { SessionLauncher, SessionV2 } from "../sessionsV2.types";
import SessionViewV2 from "../SessionView/SessionViewV2";

import styles from "./Actions.module.scss";

interface SessionItemV2Props {
launcher: SessionLauncher;
session: SessionV2;
Expand All @@ -35,6 +41,8 @@ export default function SessionItemV2({
launcher,
session,
}: SessionItemV2Props) {
const { project } = useProject();

const [hash, setHash] = useLocationHash();
const sessionHash = useMemo(
() => `session-v2-${session.name}`,
Expand All @@ -53,20 +61,44 @@ export default function SessionItemV2({

return (
<>
<ListGroupItem
action
className={cx(
"cursor-pointer",
"d-flex",
"flex-row",
"align-items-center"
)}
data-cy="session-v2-item"
tag="button"
onClick={toggleSessionView}
>
<CaretRightFill className={cx("bi", "me-1")} />
<SessionStatusV2Label session={session} />
<ListGroupItem action className="py-0" data-cy="session-launcher-item">
<Link
className={cx(
"d-flex",
"flex-column",
"gap-3",
"link-primary",
"text-body",
"text-decoration-none",
"py-3"
)}
to={{ hash: sessionHash }}
>
<Row className="g-2">
<Col
className={cx("order-1", "align-items-center", "d-flex")}
xs={12}
md={8}
lg={9}
>
<CaretRightFill className={cx("bi", "me-1")} />
<SessionStatusV2Label session={session} />
</Col>
<Col className={cx("order-3", "order-md-2")} xs={12} md={3} lg={2}>
{/* NOTE: This is a placeholder for the session actions button */}
<div className={cx("text-start", "text-md-end", "px-2", "py-1")}>
<span className="bi" />
</div>
</Col>
</Row>
</Link>
{/* NOTE: The session actions button is visually placed within the link card, but its DOM tree is kept separate. */}
<div className={cx(styles.actionsButton, "position-absolute")}>
<ActiveSessionButton
session={session}
showSessionUrl={getShowSessionUrlByProject(project, session.name)}
/>
</div>
</ListGroupItem>
<SessionViewV2
isOpen={isSessionViewOpen}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*!
* Copyright 2025 - 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 { PlayCircle } from "react-bootstrap-icons";
import { Button, UncontrolledTooltip } from "reactstrap";

import { useProject } from "../../ProjectPageV2/ProjectPageContainer/ProjectPageContainer";
import type { SessionLauncher, SessionV2 } from "../sessionsV2.types";
import StartSessionButton from "../StartSessionButton";
import { useRef } from "react";

interface SessionLauncherActionsProps {
launcher: SessionLauncher;
sessions: SessionV2[];
}

export default function SessionLauncherActions({
launcher,
sessions,
}: SessionLauncherActionsProps) {
const { project } = useProject();

if (sessions.length > 0) {
return <DisabledLaunchButton />;
}

return (
<StartSessionButton
launcherId={launcher.id}
namespace={project.namespace}
slug={project.slug}
/>
);
}

function DisabledLaunchButton() {
const ref = useRef<HTMLSpanElement>(null);

return (
<>
<span className="d-inline-block" tabIndex={0} ref={ref}>
<Button type="button" color="primary" disabled size="sm">
<PlayCircle className={cx("bi", "me-1")} />
Launch
</Button>
</span>
<UncontrolledTooltip target={ref}>
A session is already running from this launcher
</UncontrolledTooltip>
</>
);
}
116 changes: 65 additions & 51 deletions client/src/features/sessionsV2/SessionList/SessionLauncherItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import cx from "classnames";
import { useCallback, useMemo } from "react";
import { CircleFill } from "react-bootstrap-icons";
import { Link } from "react-router-dom-v5-compat";
import { Badge, Col, ListGroupItem, Row } from "reactstrap";

import useLocationHash from "../../../utils/customHooks/useLocationHash.hook";
Expand All @@ -28,6 +29,9 @@ import type { SessionLauncher } from "../sessionsV2.types";
import SessionLauncherView from "../SessionView/SessionLauncherView";
import SessionItemV2 from "./SessionItemV2";

import styles from "./Actions.module.scss";
import SessionLauncherActions from "./SessionLauncherActions";

interface SessionLauncherItemProps {
launcher: SessionLauncher;
}
Expand Down Expand Up @@ -70,59 +74,69 @@ export default function SessionLauncherItem({

return (
<>
<div className="position-relative">
<div
className={cx("position-absolute", "z-2")}
style={{
top: 0,
marginTop: "1rem",
right: 0,
}}
<ListGroupItem action className="py-0" data-cy="session-launcher-item">
<Link
className={cx(
"d-flex",
"flex-column",
"gap-3",
"link-primary",
"text-body",
"text-decoration-none",
"py-3"
)}
to={{ hash: launcherHash }}
>
<button className="my-auto">{"<ACTIONS>"}</button>
</div>
</div>
<ListGroupItem
action
className="cursor-pointer"
data-cy="session-launcher-item"
tag="button"
onClick={toggleLauncherView}
>
<Row className="g-2">
<Col
className={cx("align-items-center", "d-flex")}
xs={12}
md={8}
lg={9}
>
<Row className="g-2">
<Col
xs={12}
xl="auto"
className={cx("d-inline-block", "link-primary", "text-body")}
>
<span className="fw-bold" data-cy="session-name">
{name}
</span>
</Col>
<Col xs={12} xl="auto">
<Badge
className={cx(
"border",
"bg-success-subtle",
"border-success",
"text-success-emphasis"
)}
pill
<Row className="g-2">
<Col
className={cx("order-1", "align-items-center", "d-flex")}
xs={12}
md={8}
lg={9}
>
<Row className="g-2">
<Col
xs={12}
xl="auto"
className={cx("d-inline-block", "link-primary", "text-body")}
>
<CircleFill className={cx("bi", "me-1")} />
Ready
</Badge>
</Col>
</Row>
</Col>
</Row>
<span className="fw-bold" data-cy="session-name">
{name}
</span>
</Col>
<Col xs={12} xl="auto">
<Badge
className={cx(
"border",
"bg-success-subtle",
"border-success",
"text-success-emphasis",
"fs-small",
"fw-normal"
)}
pill
>
<CircleFill className={cx("bi", "me-1")} />
Ready
</Badge>
</Col>
</Row>
</Col>
<Col className={cx("order-3", "order-md-2")} xs={12} md={3} lg={2}>
{/* NOTE: This is a placeholder for the session actions button */}
<div className={cx("text-start", "text-md-end", "px-2", "py-1")}>
<span className="bi" />
</div>
</Col>
</Row>
</Link>
{/* NOTE: The session actions button is visually placed within the link card, but its DOM tree is kept separate. */}
<div className={cx(styles.actionsButton, "position-absolute")}>
<SessionLauncherActions
launcher={launcher}
sessions={filteredSessions}
/>
</div>
</ListGroupItem>
{filteredSessions.map((session) => (
<SessionItemV2
Expand Down
21 changes: 20 additions & 1 deletion client/src/features/sessionsV2/SessionView/SessionViewV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
import cx from "classnames";
import { Offcanvas, OffcanvasBody } from "reactstrap";

import { useProject } from "../../ProjectPageV2/ProjectPageContainer/ProjectPageContainer";
import { SessionRowResourceRequests } from "../../session/components/SessionsList";
import ActiveSessionButton from "../components/SessionButton/ActiveSessionButton";
import {
SessionStatusV2Description,
SessionStatusV2Label,
} from "../components/SessionStatus/SessionStatus";
import { getShowSessionUrlByProject } from "../SessionsV2";
import type { SessionLauncher, SessionV2 } from "../sessionsV2.types";
import { CommandCopy } from "../../../components/commandCopy/CommandCopy";

interface SessionViewV2Props {
isOpen: boolean;
Expand All @@ -40,6 +44,8 @@ export default function SessionViewV2({
launcher,
session,
}: SessionViewV2Props) {
const { project } = useProject();

return (
<Offcanvas isOpen={isOpen} toggle={toggle} direction="end" backdrop>
<OffcanvasBody>
Expand All @@ -56,7 +62,15 @@ export default function SessionViewV2({
<div className={cx("d-flex", "flex-column", "gap-4")}>
<div>
<div>
<div className={cx("float-end", "mt-1", "ms-1")}>{"<MENU>"}</div>
<div className={cx("float-end", "mt-1", "ms-1")}>
<ActiveSessionButton
session={session}
showSessionUrl={getShowSessionUrlByProject(
project,
session.name
)}
/>
</div>
<h2
className={cx("m-0", "text-break")}
data-cy="session-view-title"
Expand All @@ -76,6 +90,11 @@ export default function SessionViewV2({
resourceRequests={session.resources.requests}
/>
</div>

<div>
<h4 className={cx("mb-0", "me-2")}>Container image</h4>
<CommandCopy command={session.image} />
</div>
</div>
</OffcanvasBody>
</Offcanvas>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ export default function ActiveSessionButton({

return (
<ButtonWithMenuV2
className={cx(className)}
className={className}
color="primary"
default={defaultAction}
preventPropagation
Expand Down

0 comments on commit f6b195f

Please sign in to comment.