Skip to content

Commit

Permalink
Add saving and loading of pieces layouts for gallery view
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew7li committed Aug 3, 2023
1 parent acdc9af commit be80001
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 80 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3

## Authors

This is made by Full Stack at Brown. Abcd
This is made by Full Stack at Brown.

## What's next? How do I make an app with this?

Expand Down
9 changes: 9 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,12 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model Layout {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
layout Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
5 changes: 1 addition & 4 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import Link from "next/link";
import InstagramIcon from "@mui/icons-material/Instagram";
import LinkedInIcon from "@mui/icons-material/LinkedIn";
import YouTubeIcon from "@mui/icons-material/YouTube";
import { Box, Container, Divider, Grid } from "@mui/material";
import { Box, Container, Grid } from "@mui/material";

export default function Footer() {
return (
<>
{/* <Divider
sx={{ borderBottomWidth: 1, borderColor: "black", marginTop: "1rem" }}
/> */}
<Box
sx={{
width: "100%",
Expand Down
55 changes: 55 additions & 0 deletions src/components/layouts/PiecesResponsiveGridRead.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Content } from "@prisma/client";
import Link from "next/link";

import { type Layouts, Responsive, WidthProvider } from "react-grid-layout";
import "react-grid-layout/css/styles.css";
import "react-resizable/css/styles.css";

const ResponsiveGridLayout = WidthProvider(Responsive);

interface PiecesResponsiveGridReadProps {
pieces: Content[];
layout: Layouts;
}

export default function PiecesResponsiveGridRead({
pieces,
layout,
}: PiecesResponsiveGridReadProps) {
console.log(layout);
return (
<ResponsiveGridLayout
className="layout basis-full"
rowHeight={300}
width={1000}
layouts={layout}
breakpoints={{ lg: 1200, md: 768, sm: 480, xs: 0 }}
cols={{ lg: 6, md: 6, sm: 4, xs: 2 }}
>
{pieces.map((piece) => {
return (
<div
key={piece.id}
data-grid={{
x: 0,
y: 0,
w: 3,
h: 1,
maxW: 3,
maxH: 10,
static: true,
}}
className="cursor-pointer transition duration-500 hover:scale-110"
>
<Link href={`/pieces/${piece.id}`} key={piece.id}>
<img
src={piece.imgURL}
className="h-full w-full bg-gray-50 object-cover"
/>
</Link>
</div>
);
})}
</ResponsiveGridLayout>
);
}
55 changes: 55 additions & 0 deletions src/components/layouts/PiecesResponsiveGridWrite.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Content } from "@prisma/client";

import {
type Layout,
type Layouts,
Responsive,
WidthProvider,
} from "react-grid-layout";
import "react-grid-layout/css/styles.css";
import "react-resizable/css/styles.css";

const ResponsiveGridLayout = WidthProvider(Responsive);

interface PiecesResponsiveGridWriteProps {
pieces: Content[];
}

export default function PiecesResponsiveGridWrite({
pieces,
}: PiecesResponsiveGridWriteProps) {
const getLayouts = (): Layouts => {
const savedLayouts = localStorage.getItem("layout");
return savedLayouts ? (JSON.parse(savedLayouts) as Layouts) : {};
};

const handleLayoutChange = (layout: Layout[], layouts: Layouts) => {
localStorage.setItem("layout", JSON.stringify(layouts));
};

return (
<ResponsiveGridLayout
className="layout basis-full"
rowHeight={300}
width={1000}
layouts={getLayouts()}
onLayoutChange={handleLayoutChange}
breakpoints={{ lg: 1200, md: 768, sm: 480, xs: 0 }}
cols={{ lg: 6, md: 6, sm: 4, xs: 2 }}
>
{pieces.map((piece) => {
return (
<div
key={piece.id}
className="cursor-pointer transition duration-500 hover:scale-110"
>
<img
src={piece.imgURL}
className="h-full w-full bg-gray-50 object-cover"
/>
</div>
);
})}
</ResponsiveGridLayout>
);
}
8 changes: 4 additions & 4 deletions src/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export const env = createEnv({
// Add `.min(1) on ID and SECRET if you want to make sure they're not empty
GOOGLE_CLIENT_ID: z.string(),
GOOGLE_CLIENT_SECRET: z.string(),
// UPLOADTHING_SECRET: z.string().min(1),
// UPLOADTHING_APP_ID: z.string().min(1),
UPLOADTHING_SECRET: z.string().min(1),
UPLOADTHING_APP_ID: z.string().min(1),
},

/**
Expand All @@ -45,8 +45,8 @@ export const env = createEnv({
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
JWT_SECRET: process.env.JWT_SECRET,
// UPLOADTHING_SECRET: process.env.UPLOADTHING_SECRET,
// UPLOADTHING_APP_ID: process.env.UPLOADTHING_APP_ID,
UPLOADTHING_SECRET: process.env.UPLOADTHING_SECRET,
UPLOADTHING_APP_ID: process.env.UPLOADTHING_APP_ID,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
Expand Down
2 changes: 1 addition & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Home: NextPage = () => {

<section
id="home"
className="flex min-h-screen items-center justify-center"
className="flex h-[calc(100vh-5rem)] items-center justify-center"
style={{
backgroundImage: `linear-gradient(
120deg,
Expand Down
154 changes: 84 additions & 70 deletions src/pages/pieces/index.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,71 @@
import { api } from "@CarteBlanche/utils/api";
import { type NextPage } from "next";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useState } from "react";

import CircularSpinner from "@CarteBlanche/components/CircularSpinner";
import PiecesSidebar from "@CarteBlanche/components/PiecesSidebar";
import TopNav from "@CarteBlanche/components/TopNav";
import { Button } from "@mui/material";
import { Responsive, WidthProvider } from "react-grid-layout";
import PiecesResponsiveGridRead from "@CarteBlanche/components/layouts/PiecesResponsiveGridRead";
import PiecesResponsiveGridWrite from "@CarteBlanche/components/layouts/PiecesResponsiveGridWrite";
import { Button, Modal } from "@mui/material";

import { ErrorSnackbar } from "@CarteBlanche/components/forms/snackbars/ErrorSnackbar";
import { SuccessSnackbar } from "@CarteBlanche/components/forms/snackbars/SuccessSnackbar";
import type { Layouts } from "react-grid-layout";
import "react-grid-layout/css/styles.css";
import "react-resizable/css/styles.css";

const ResponsiveGridLayout = WidthProvider(Responsive);
import { FormErrorMessage } from "@CarteBlanche/components/forms/FormErrorMessage";

const Pieces: NextPage = () => {
// States
const [openSuccessSnackbar, setOpenSuccessSnackbar] = useState(false);
const [successSnackbarMessage, setSuccessSnackbarMessage] = useState("");
const [openErrorSnackbar, setOpenErrorSnackbar] = useState(false);
const [errorSnackbarMessage, setErrorSnackbarMessage] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [isEditing, setIsEditing] = useState(false);

// API Calls
const { data: session } = useSession();

const { data: user } = api.user.getUser.useQuery(
{ id: session?.user.id },
{ refetchOnWindowFocus: false }
);

const [isEditing, setIsEditing] = useState(false);

const [colScale, setColScale] = useState(12);
const maxPiecesPerRow = 3;
useEffect(() => {
const handleResize = () => {
if (window.innerWidth > 1200) {
setColScale(12 / maxPiecesPerRow);
} else if (window.innerWidth > 996) {
setColScale(10 / maxPiecesPerRow);
} else if (window.innerWidth > 768) {
setColScale(6 / maxPiecesPerRow);
} else if (window.innerWidth > 480) {
setColScale(4 / maxPiecesPerRow);
} else {
setColScale(2 / maxPiecesPerRow);
}
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
const { mutate: upsertLayout, isLoading: isUpsertingLayout } =
api.layout.upsertLayout.useMutation({
onError(error) {
setOpenErrorSnackbar(true);
setErrorSnackbarMessage(error.message);
setErrorMessage(error.message);
},
onSuccess() {
setOpenSuccessSnackbar(true);
setSuccessSnackbarMessage("Layout successfully saved!");
setIsEditing(false);
setErrorMessage("");
},
});

/**
* Handles saving of the grid layout
*/
const saveGridLayout = () => {
alert("Not implemented yet!");
setIsEditing(false);
const layout = localStorage.getItem("layout");
if (!layout) {
setErrorMessage("Local storage for grid layout is empty!");
setOpenErrorSnackbar(true);
setErrorSnackbarMessage("Error saving layout!");
return;
}
const layoutToSave = {
name: "pieces",
layout: JSON.parse(layout) as Layouts,
};

upsertLayout(layoutToSave);
};

const {
Expand All @@ -61,64 +77,62 @@ const Pieces: NextPage = () => {
{ refetchOnWindowFocus: false }
);

if (isLoading || !pieces) {
const {
data: layout,
isLoading: isLoadingLayout,
error: errorLayout,
} = api.layout.getLayout.useQuery(
{ name: "pieces" },
{ refetchOnWindowFocus: false }
);

if (isLoading || isLoadingLayout || !pieces) {
return <CircularSpinner />;
}
if (error) {
return <p>Oh no... {error.message}</p>;
}
if (errorLayout) {
return <p>Oh no... {errorLayout.message}</p>;
}
return (
<>
<TopNav />
{isUpsertingLayout && (
<Modal open={true}>
<CircularSpinner />
</Modal>
)}
<SuccessSnackbar
isOpen={openSuccessSnackbar}
onClose={() => setOpenSuccessSnackbar(false)}
message={successSnackbarMessage}
/>
<ErrorSnackbar
isOpen={openErrorSnackbar}
onClose={() => setOpenErrorSnackbar(false)}
message={errorSnackbarMessage}
/>
{isEditing && (
<div className="bg-cyan-300">
<h1>EDITING MODE</h1>
{errorMessage && (
<FormErrorMessage errorMessage={`Error: ${errorMessage}`} />
)}
<Button onClick={() => setIsEditing(!isEditing)}>Cancel</Button>
<Button onClick={saveGridLayout}>Save</Button>
</div>
)}
<div className="flex min-h-screen">
<PiecesSidebar user={user ?? undefined} setIsEditing={setIsEditing} />
<ResponsiveGridLayout
key={isEditing.toString()}
className="layout basis-3/4"
rowHeight={30}
width={1200}
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
>
{pieces.map((piece, idx) => {
return (
<div
key={piece.id}
data-grid={{
x: 0,
y: 0,
w: 3,
h: 8,
maxW: 3,
maxH: 10,
static: !isEditing,
}}
className="cursor-pointer transition duration-500 hover:scale-110"
>
{isEditing ? (
<img
src={piece.imgURL}
className="h-full w-full bg-gray-50 object-cover"
/>
) : (
<Link href={`/pieces/${piece.id}`} key={piece.id}>
<img
src={piece.imgURL}
className="h-full w-full bg-gray-50 object-cover"
/>
</Link>
)}
</div>
);
})}
</ResponsiveGridLayout>
{isEditing ? (
<PiecesResponsiveGridWrite pieces={pieces} />
) : (
<PiecesResponsiveGridRead
pieces={pieces}
layout={layout.layout as Layouts}
/>
)}
</div>
</>
);
Expand Down
Loading

0 comments on commit be80001

Please sign in to comment.