Skip to content
Open
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
3 changes: 2 additions & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@
"preview": "vite preview"
},
"dependencies": {
"@kobalte/core": "^0.13.13",
"@formisch/solid": "1.0.0",
"@kobalte/core": "^0.13.13",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^1.0.0",
"@solidjs/start": "^2.0.0",
"nitro": "3.0.260610-beta",
"relay-runtime": "^21.0.1",
"seroval": "1.5.4",
"solid-js": "^1.9.5",
"solid-relay": "1.0.0-beta.29",
"valibot": "1.4.2",
Expand Down
173 changes: 161 additions & 12 deletions packages/web/src/routes/workspace/create/[instance_id]/actors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import { Field, Form, type SubmitHandler, createForm } from "@formisch/solid";
import { Button } from "@kobalte/core/button";
import { NumberField } from "@kobalte/core/number-field";
import { Title } from "@solidjs/meta";
Expand All @@ -23,20 +24,25 @@ import {
query,
} from "@solidjs/router";
import { graphql } from "relay-runtime";
import { Show, createSignal } from "solid-js";
import {
createMutation,
createPreloadedQuery,
loadQuery,
useRelayEnvironment,
} from "solid-relay";
import * as v from "valibot";

import type { GetHostQuery } from "./__generated__/GetHostQuery.graphql.tsx";
import type { GenerateActorsMutation } from "./__generated__/GenerateActorsMutation.graphql.ts";
import type { GetHostQuery } from "./__generated__/GetHostQuery.graphql.ts";

import styles from "~/styles/form.module.css";

const getHostQuery = graphql`
query GetHostQuery($instanceId: ID!) {
instance: node(id: $instanceId) {
... on Instance {
id
host
}
}
Expand All @@ -62,12 +68,119 @@ export const route = {

type RouteData = ReturnType<typeof loadInstanceQuery>;

const generateActorsMutation = graphql`
mutation GenerateActorsMutation($instance: ID!, $size: Int!) {
generateActors(instance: $instance, size: $size) {
resultType: __typename
... on CreateActorsSuccess {
actors {
id
uuid
username
handle
iri
}
}
... on CreateActorsError {
type
message
}
}
}
`;
const generateActorsSchema = v.object({
size: v.pipe(
v.number("Enter the number of actors."),
v.integer("The number of actors must be a whole number."),
v.minValue(1, "Create at least one actor."),
),
});

export default function CreateActorsPage(props: RouteSectionProps<RouteData>) {
const [errorMessage, setErrorMessage] = createSignal<string>();
const [commitGenerateActors, isGeneratingActors] =
createMutation<GenerateActorsMutation>(generateActorsMutation);

const data = createPreloadedQuery<GetHostQuery>(
getHostQuery,
() => props.data,
);

const generateActorsForm = createForm({
schema: generateActorsSchema,
initialInput: {
size: 1,
},
});

const submit: SubmitHandler<typeof generateActorsSchema> = ({ size }) => {
const instance = data()?.instance?.id;

if (instance === undefined) {
setErrorMessage("Instance not found.");
return;
}
setErrorMessage(undefined);
commitGenerateActors({
variables: { instance, size },
onCompleted: (response, errors) => {
const graphQLErrors = errors ?? [];
if (graphQLErrors.length > 0) {
setErrorMessage(
graphQLErrors.map((error) => error.message).join("\n"),
);
return;
}

const result = response.generateActors;
switch (result.resultType) {
case "CreateActorsSuccess": {
//FIXME: pop up success message
return;
}
case "CreateActorsError": {
switch (result.type) {
case "InvalidSize": {
setErrorMessage("Enter at least one actor.");
return;
}
case "InstanceNotFound": {
setErrorMessage(
"The selected instance could not be found or is no longer available.",
);
return;
}
case "TooManyActors": {
setErrorMessage(result.message);
return;
}
case "%future added value": {
setErrorMessage(
"The server returned an unsupported actor-generation error.",
);
return;
}
default: {
setErrorMessage("Unable to generate actors.");
return;
}
}
}
case "%other": {
setErrorMessage("Unable to generate actors.");
return;
}
default: {
setErrorMessage("Unable to generate actors.");
}
}
},
onError: (error) => {
setErrorMessage(error.message);
},
});
};

return (
<main class={styles.page}>
<Title>Create Actors</Title>
Expand All @@ -76,19 +189,55 @@ export default function CreateActorsPage(props: RouteSectionProps<RouteData>) {
<h1 id="create-actors-title">Create Actors</h1>
<p>Selected Instance: {data()?.instance?.host}</p>
</header>
<form class={styles.form}>
<NumberField class={styles.field} name="actors" minValue={1}>
<NumberField.Label>The Number of Actors</NumberField.Label>
<NumberField.Input class={styles.input} placeholder="5" required />
<NumberField.Description class={styles.hint}>
DrFed generates actors in {data()?.instance?.host}.
</NumberField.Description>
</NumberField>

<Button class={styles.button} type="submit">
<Form class={styles.form} of={generateActorsForm} onSubmit={submit}>
<Field of={generateActorsForm} path={["size"]}>
{(field) => (
<NumberField
class={styles.field}
name={field.props.name}
value={field.input ?? ""}
minValue={1}
onRawValueChange={field.onInput}
>
<NumberField.Label class={styles.field}>Size</NumberField.Label>
<NumberField.Input
ref={field.props.ref}
onFocus={field.props.onFocus}
onBlur={field.props.onBlur}
class={styles.input}
aria-invalid={Boolean(field.errors)}
/>
<NumberField.Description class={styles.hint}>
DrFed generates actors in {data()?.instance?.host}
</NumberField.Description>
<Show when={field.errors}>
{(errors) => (
<span
class={`${styles.notice} ${styles.error}`}
role="alert"
>
{errors()[0]}
</span>
)}
</Show>
</NumberField>
)}
</Field>
<Button
class={styles.button}
type="submit"
disabled={
isGeneratingActors() || data()?.instance?.id === undefined
}
>
Create Actors
</Button>
</form>
</Form>
<Show when={errorMessage()}>
<p class={`${styles.notice} ${styles.error}`} role="alert">
{errorMessage()}
</p>
</Show>
</section>
</main>
);
Expand Down
Loading