Skip to content

Commit

Permalink
make workspace ui work: initial, create and removal ui
Browse files Browse the repository at this point in the history
dumb polling to keep specs updated, prop drilling and no
url editing.

nexts steps are using a context and allowing spec to open
so urls can be written.
  • Loading branch information
Mazuh committed Jan 31, 2024
1 parent f68a4d8 commit cfeb39c
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 23 deletions.
183 changes: 166 additions & 17 deletions src/features/project-workspace/ProjectWorkspacePage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { useEffect, useState } from "react";
import { SyntheticEvent, useEffect, useState } from "react";
import { useParams } from "wouter";
import { validate as validateUuid } from "uuid";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowLeft, faFolderOpen } from "@fortawesome/free-solid-svg-icons";
import {
faArrowLeft,
faFileCode,
faFolderOpen,
faTrash,
} from "@fortawesome/free-solid-svg-icons";
import { AppPageTemplate } from "@/components/template/AppPageTemplate";
import { Anchor, Title } from "@/components/ui/typography";
import { Project } from "@/entities/management";
import { retrieveProject } from "./opfs-project-service";
import { Project, ProjectRequestSpec } from "@/entities/management";
import {
createRequestSpec,
removeRequestSpec,
retrieveProject,
} from "./opfs-project-service";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";

export function ProjectWorkspacePage() {
const params = useParams();
Expand All @@ -19,9 +38,16 @@ export function ProjectWorkspacePage() {
return;
}

retrieveProject(projectUUID)
.then((retrieved) => setProject(retrieved))
.catch((error) => console.error("Error retrieving project.", error));
const runProjectRetrieval = () => {
retrieveProject(projectUUID)
.then((retrieved) => setProject(retrieved))
.catch((error) => console.error("Error retrieving project.", error));
};

runProjectRetrieval();

const worker = setInterval(() => runProjectRetrieval(), 500);
return () => clearInterval(worker);
}, [projectUUID]);

if (!projectUUID) {
Expand Down Expand Up @@ -50,16 +76,139 @@ export function ProjectWorkspacePage() {
</small>
</Anchor>
</p>
<Title>
<FontAwesomeIcon icon={faFolderOpen} />
<span className="pl-1">
Project workspace:{" "}
<code className="pl-2" title={project.uuid}>
{project.name}
</code>
</span>
</Title>
<p>Wadda wadda.</p>
<WorkspaceHeader project={project} />
<RequestsSpecsList project={project} />
</AppPageTemplate>
);
}

function WorkspaceHeader(props: { project: Project }) {
if (!props.project.specs.length) {
return null;
}

return (
<Title>
<FontAwesomeIcon icon={faFolderOpen} />
<span className="pl-1">
Project workspace:{" "}
<code className="px-2" title={props.project.uuid}>
{props.project.name}
</code>
<CreateRequestSpecButton projectUuid={props.project.uuid} />
</span>
</Title>
);
}

function RequestsSpecsList(props: { project: Project }) {
const [hovered, setHovered] = useState<string | null>(null);

if (props.project.specs.length === 0) {
return (
<div className="w-fit m-auto flex flex-col text-center">
<Title>✨ Created. ✨</Title>
<p className="mb-2 w-full text-center">
<em>{props.project.name}</em>
</p>
<br />
<div className="w-full flex justify-center">
<CreateRequestSpecButton projectUuid={props.project.uuid} />
</div>
</div>
);
}

return (
<div>
<ul className="list-disc pl-4 mt-2">
{props.project.specs.map((spec) => (
<li
key={spec.uuid}
onMouseEnter={() => setHovered(spec.uuid)}
onMouseLeave={() => setHovered(null)}
>
<span className="py-4">
<RequestSpecText spec={spec} />
</span>
<span
className={cn(
"ml-5",
hovered === spec.uuid ? "visible" : "invisible"
)}
>
<RequestSpecRemovalButton
projectUuid={props.project.uuid}
spec={spec}
/>
</span>
</li>
))}
</ul>
</div>
);
}

function CreateRequestSpecButton(props: { projectUuid: string }) {
const handleClick = () =>
createRequestSpec({ projectUuid: props.projectUuid });

return (
<Button onClick={handleClick}>
<FontAwesomeIcon icon={faFileCode} />
<span className="pl-1">Create request spec</span>
</Button>
);
}

function RequestSpecRemovalButton(props: {
projectUuid: string;
spec: ProjectRequestSpec;
}) {
const [isOpen, setIsOpen] = useState(false);
const open = () => setIsOpen(true);
const close = () => setIsOpen(false);

const handleSubmit = (event: SyntheticEvent) => {
event.preventDefault();
removeRequestSpec({
projectUuid: props.projectUuid,
removing: props.spec.uuid,
}).then(close);
};

return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Button variant="outline-destructive" onClick={open} title="Remove">
<FontAwesomeIcon icon={faTrash} aria-label="Remove" />
</Button>
<DialogContent>
<DialogHeader>
<DialogTitle>Request spec removal</DialogTitle>
<DialogDescription>
Are you sure you want to remove{" "}
<RequestSpecText spec={props.spec} />?
</DialogDescription>
<DialogFooter className="pt-4">
<form onSubmit={handleSubmit}>
<Button variant="outline" onClick={close}>
Cancel
</Button>
<Button type="submit" variant="destructive">
Remove request spec
</Button>
</form>
</DialogFooter>
</DialogHeader>
</DialogContent>
</Dialog>
);
}

function RequestSpecText(props: { spec: ProjectRequestSpec }) {
return (
<span>
<code>{props.spec.method}</code> <code>{props.spec.url || "..."}</code>
</span>
);
}
6 changes: 3 additions & 3 deletions src/features/project-workspace/opfs-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function createRequestSpec(
/** Params for `removeRequestSpec` */
export interface RemoveRequestSpecParams {
projectUuid: Project["uuid"];
removingUuid: ProjectRequestSpec["uuid"];
removing: ProjectRequestSpec["uuid"];
}

/**
Expand All @@ -57,11 +57,11 @@ export async function removeRequestSpec(
const project = await retrieveProject(params.projectUuid);
const updatedProject: Project = {
...project,
specs: project.specs.filter((spec) => spec.uuid !== params.removingUuid),
specs: project.specs.filter((spec) => spec.uuid !== params.removing),
};
await persistProject(updatedProject);

return { specUuid: params.removingUuid };
return { specUuid: params.removing };
}

/** Params for `patchRequestSpec` */
Expand Down
2 changes: 1 addition & 1 deletion src/services/opfs-projects-shared-internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function getListingItemFromFilename(
export const PROJECTS_OPFS_SUBDIRECTORY = "projects";

function hygienizeProjectName(name: string): string {
return name.trim().replace(".json", "");
return name.trim().replaceAll(".json", "").replaceAll('"', "");
}

async function retrieveProjectFilenameByUuid(uuid: string): Promise<string> {
Expand Down
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"compilerOptions": {
"target": "ES2020",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,

Expand Down

0 comments on commit cfeb39c

Please sign in to comment.