Set up photo editor and clean up styling

This commit is contained in:
2025-09-21 19:08:08 +01:00
parent 42caeb8834
commit 784f7320a1
19 changed files with 9067 additions and 1435 deletions

View File

@@ -1,17 +1,18 @@
"use client";
import type { PhotoData } from "@/server/api/routers/photos/list";
import { api } from "@/trpc/react";
import { zodResolver } from "@hookform/resolvers/zod";
import Image from "next/image";
import type React from "react";
import { useState } from "react";
import ImageSvg from "./file-svg";
import DirSvg from "./dir-svg";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import z from "zod";
import type { PhotoData } from "@/server/api/routers/photos/list";
import { api } from "@/trpc/react";
import DirSvg from "./dir-svg";
import ImageSvg from "./file-svg";
import Tiptap from "./photo-editor";
// - TODO - Pull this from trpc
const FormSchema = z.object({
title: z
.string()
@@ -83,24 +84,35 @@ function renderTree(node: DirectoryTree, pathSoFar = ""): Item[] {
return items;
}
function RenderLeaf(leaf: Item[], selectImageTab: (path: string) => void) {
function RenderLeaf(
leaf: Item[],
selectImageTab: (path: string) => void,
selectedImage: PhotoData | undefined,
) {
return leaf.map((leaf) => {
const selectedLeaf =
`https://fly.storage.tigris.dev/joemonk-photos/${leaf.fullPath}` ===
selectedImage?.src;
if (leaf.children?.length) {
return (
<li>
<li key={leaf.fullPath}>
<details open>
<summary>
<DirSvg />
{leaf.name}
</summary>
<ul>{RenderLeaf(leaf.children, selectImageTab)}</ul>
<ul>{RenderLeaf(leaf.children, selectImageTab, selectedImage)}</ul>
</details>
</li>
);
}
return (
<li key={leaf.fullPath}>
<button type="button" onClick={() => selectImageTab(leaf.fullPath)}>
<button
type="button"
className={selectedLeaf ? "active" : ""}
onClick={() => selectImageTab(leaf.fullPath)}
>
<ImageSvg />
{leaf.name}
</button>
@@ -111,9 +123,16 @@ function RenderLeaf(leaf: Item[], selectImageTab: (path: string) => void) {
export function PhotoTab(): React.JSX.Element {
const [selectedImage, setSelectedImage] = useState<PhotoData>();
const query = api.photos.list.useQuery(undefined, {
refetchOnWindowFocus: false,
});
const listQuery = api.photos.list.useInfiniteQuery(
{},
{
getNextPageParam: (lastPage) => lastPage.next,
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
},
);
const modifyMutate = api.photos.modify.useMutation();
const {
register,
@@ -125,18 +144,21 @@ export function PhotoTab(): React.JSX.Element {
mode: "onSubmit",
});
if (query.isLoading) {
if (listQuery.isLoading) {
return <p>Loading</p>;
}
if (query.error) {
return <p>{query.error.message}</p>;
if (listQuery.error) {
return <p>{listQuery.error.message}</p>;
}
const images = query.data?.data;
const images = listQuery.data?.pages.flatMap((data) => data.data);
if (!images || images?.length === 0) {
return <p>No Images</p>;
}
if (listQuery.hasNextPage) {
listQuery.fetchNextPage();
}
const selectImageTab = (path: string) => {
const selectImage = (path: string) => {
const img = images.find(
(img) =>
img.src === `https://fly.storage.tigris.dev/joemonk-photos/${path}`,
@@ -153,22 +175,26 @@ export function PhotoTab(): React.JSX.Element {
);
const renderedTree = renderTree(tree);
const onSubmit = (data: IFormInput) => {
console.log(data);
};
return (
<div className="flex w-full gap-2">
<ul className="menu menu-xs bg-base-200 box w-1/4">
{RenderLeaf(renderedTree, selectImageTab)}
<div className="flex w-full gap-4 md:gap-2 flex-col md:flex-row">
<ul className="menu menu-xs bg-base-200 box w-full md:w-1/4">
{RenderLeaf(renderedTree, selectImage, selectedImage)}
</ul>
<div className="w-3/4 box border border-base-300 p-2">
<div className="md:w-3/4 box border border-base-300 p-2 w-full">
{selectedImage?.src ? (
<form onSubmit={handleSubmit(onSubmit)}>
<form
onSubmit={handleSubmit((data) =>
modifyMutate.mutate({
title: data.title,
description: data.description,
src: selectedImage.src,
}),
)}
>
<label
className={`floating-label input text-lg mb-2 w-full ${errors.title ? "input-error" : null}`}
>
<span>{`Title ${errors.title ? " - " + errors.title.message : ""}`}</span>
<span>{`Title ${errors.title ? ` - ${errors.title.message}` : ""}`}</span>
<input
{...register("title")}
type="text"
@@ -263,13 +289,30 @@ export function PhotoTab(): React.JSX.Element {
control={control}
name="description"
render={({ field: { onChange } }) => (
<Tiptap onChange={onChange} />
<Tiptap
onChange={onChange}
initContent={selectedImage.description}
/>
)}
/>
</div>
<button className="button" type="submit">
Submit
</button>
<div className="flex flex-row items-center">
<button
className="btn btn-primary flex self-center m-4"
type="submit"
>
Save
</button>
{modifyMutate.isSuccess ? (
<p className="badge badge-success">Updated</p>
) : modifyMutate.isError ? (
<p className="badge badge-error">
Error: {modifyMutate.error.message}
</p>
) : modifyMutate.isPending ? (
<p className="badge badge-info">Updating</p>
) : null}
</div>
</form>
) : null}
</div>