Skip to content

Commit

Permalink
Rework kit navigation, improve kit header
Browse files Browse the repository at this point in the history
  • Loading branch information
tomcur committed Aug 15, 2023
1 parent acb429c commit 290cbcd
Show file tree
Hide file tree
Showing 34 changed files with 799 additions and 253 deletions.
84 changes: 84 additions & 0 deletions astroplant-frontend/src/scenes/kit/Configurations.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
.top {
display: flex;
justify-content: end;
margin-bottom: 1rem;
}

.listContainer {
border: 1px solid var(--border-color);
border-radius: 0.5rem;

header {
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
background-color: var(--bg-darker);
border-bottom: 1px solid var(--border-color);
padding: 0.4rem 1rem;
}

em {
padding-inline: 1rem;
}

&:not(:last-child) {
margin-bottom: 1rem;
}
}

.list {
list-style: none;
margin: 0;
padding: 0;

li {
display: flex;
align-items: center;
padding: 0.4rem 1rem;

&:not(:last-child) {
border-bottom: 1px solid var(--border-color);
}

div {
flex: 1;
}
}
}

.description {
display: flex;
align-items: center;
}

.identifier {
margin-left: 0.5rem;
font-size: 0.8em;
}

.name {
border-radius: 0.5rem;
}

.usageIndicator {
flex: 1;
text-align: right;
color: var(--green-500);
}

.actions {
display: flex;
justify-content: right;
align-items: center;
gap: 1rem;
}

.shortcuts {
display: flex;
gap: 0.5rem;
font-size: 0.8rem;

/* TODO: remove this when using a different icon set */
i {
height: initial;
}
}
210 changes: 210 additions & 0 deletions astroplant-frontend/src/scenes/kit/Configurations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { useMemo } from "react";
import { Link, Route, Routes } from "react-router-dom";
import { Icon } from "semantic-ui-react";
import { useTranslation } from "react-i18next";

import { useAppDispatch, useAppSelector } from "~/hooks";
import {
KitConfigurationState,
KitState,
configurationsById,
} from "~/modules/kit/reducer";
import ApiButton from "~/Components/ApiButton";
import { Response, api, schemas } from "~/api";
import {
kitConfigurationUpdated,
kitSetAllConfigurationsInactive,
} from "~/modules/kit/actions";
import { Button, ButtonLink } from "~/Components/Button";

import style from "./Configurations.module.css";
import CreateConfiguration from "./CreateConfiguration";

const Button_ = ApiButton<any>();

export type ConfigurationsProps = {
kit: KitState;
};

function ConfigurationRow({
kit,
configuration,
showActivate,
}: {
kit: KitState;
configuration: KitConfigurationState;
showActivate: boolean;
}) {
const { t } = useTranslation();
const dispatch = useAppDispatch();

const onResponse = (response: Response<schemas["KitConfiguration"]>) => {
dispatch(
kitSetAllConfigurationsInactive({
serial: kit.serial,
kitId: kit.details?.id!,
}),
);
dispatch(
kitConfigurationUpdated({
serial: kit.serial,
configuration: response.data,
}),
);
alert(
"Configuration updated. Make sure to restart the kit for the configuration to activate.",
);
};

const send = () => {
return api.patchConfiguration({
configurationId: configuration.id,
patchKitConfiguration: {
active: !configuration.active,
},
});
};

return (
<li>
<div className={style.description}>
<span className={style.name}>
<Link to={`../data?c=${configuration.id}`}>
{configuration.description || "Unnamed configuration"}
</Link>
</span>
<span className={style.identifier} title="Identifier">
#{configuration.id}
</span>
<span className={style.usageIndicator}>
{configuration.neverUsed && (
<Icon
name="asterisk"
alt="Never activated"
title="Never activated"
/>
)}
</span>
</div>
<div className={style.actions}>
<span className={style.view}>
<ButtonLink
variant="muted"
size="small"
to={`../data/configuration?c=${configuration.id}`}
>
{configuration.neverUsed ? "Edit" : "View"}
</ButtonLink>
</span>
<span className={style.shortcuts}>
<ButtonLink
variant="text"
title="Delete this configuration"
to={`../data/danger?c=${configuration.id}`}
>
<Icon name="trash" />
</ButtonLink>
{showActivate && (
<Button_
buttonProps={{
variant: "text",
title: "Activate this configuration",
}}
send={send}
onResponse={onResponse}
label={t("common.activate")}
confirm={() => ({
content: t(
configuration.neverUsed
? "kitConfiguration.activateConfirmNeverUsed"
: "kitConfiguration.activateConfirm",
{
kitName: kit.details?.name ?? "Unnamed kit",
configurationDescription:
configuration.description ?? "Unnamed configuration",
},
),
})}
>
<Icon name="play" />
</Button_>
)}
</span>
</div>
</li>
);
}

export function Configurations({ kit }: ConfigurationsProps) {
let configurations_ = useAppSelector((state) =>
configurationsById(state, kit.configurations),
);
const configurations: KitConfigurationState[] = useMemo(() => {
return configurations_.filter(
// This is clearly safe, but the TS checker doesn't actually prove type safety here :/
// Change the inequality into equality and it still compiles.
(c): c is KitConfigurationState => c !== undefined,
);
}, [configurations_]);

const activeConfiguration = useMemo(
() => configurations.find((c) => c.active) ?? null,
[configurations],
);
const inactiveConfigurations = useMemo(
() => configurations.filter((c) => !c.active) ?? null,
[configurations],
);

return (
<Routes>
<Route
path="/"
element={
<article>
<section className={style.top}>
<ButtonLink to="create" variant="primary">New configuration</ButtonLink>
</section>
<section className={style.listContainer}>
<header>
<h3>Active</h3>
</header>
{activeConfiguration ? (
<ul className={style.list}>
<ConfigurationRow
key={activeConfiguration.id}
kit={kit}
configuration={activeConfiguration}
showActivate={false}
/>
</ul>
) : (
<em>There is no active configuration</em>
)}
</section>
<section className={style.listContainer}>
<header>
<h3>Inactive</h3>
</header>
{inactiveConfigurations.length > 0 ? (
<ul className={style.list}>
{inactiveConfigurations.map((conf) => (
<ConfigurationRow
key={conf.id}
kit={kit}
configuration={conf}
showActivate={true}
/>
))}
</ul>
) : (
<em>There are no inactive configurations</em>
)}
</section>
</article>
}
/>
<Route path="/create" element={<CreateConfiguration kit={kit} />} />
</Routes>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@ import { Container, Segment } from "semantic-ui-react";

import { JSONSchema7 } from "json-schema";
import ApiForm from "~/Components/ApiForm";

import { kitConfigurationCreated } from "~/modules/kit/actions";

import { KitContext } from "../../contexts";
import { Response, api, schemas } from "~/api";
import { useAppDispatch } from "~/hooks";
import { KitState } from "~/modules/kit/reducer";

const CreateConfigurationForm = ApiForm<
any,
Response<schemas["KitConfiguration"]>
>();

export default function CreateConfiguration() {
export type CreateConfigurationProps = {
kit: KitState;
};

export default function CreateConfiguration({ kit }: CreateConfigurationProps) {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const kit = useContext(KitContext);

const [done, setDone] = useState(false);
const [result, setResult] = useState<schemas["KitConfiguration"] | null>(
Expand Down Expand Up @@ -62,7 +63,13 @@ export default function CreateConfiguration() {
<Container text>
<Segment padded>
{done ? (
<Navigate to={`../${result!.id}`} replace />
<Navigate
to={{
pathname: "../../data/configuration",
search: `?c=${result!.id}`,
}}
replace
/>
) : (
<CreateConfigurationForm
idPrefix="createConfigurationForm"
Expand Down
36 changes: 0 additions & 36 deletions astroplant-frontend/src/scenes/kit/configure/index.tsx

This file was deleted.

Loading

0 comments on commit 290cbcd

Please sign in to comment.