diff --git a/package-lock.json b/package-lock.json
index 3dc3ed2..0421d81 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,6 +20,7 @@
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-switch": "^1.2.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -2792,6 +2793,35 @@
}
}
},
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz",
+ "integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
diff --git a/package.json b/package.json
index 18a0da5..a359c9f 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-switch": "^1.2.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
diff --git a/prisma/migrations/20250628155128_image_nsfw/migration.sql b/prisma/migrations/20250628155128_image_nsfw/migration.sql
new file mode 100644
index 0000000..d9ffd3f
--- /dev/null
+++ b/prisma/migrations/20250628155128_image_nsfw/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Image" ADD COLUMN "nsfw" BOOLEAN NOT NULL DEFAULT false;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 398340b..f3ff274 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -112,6 +112,7 @@ model Image {
imageName String
originalFile String
uploadDate DateTime @default(now())
+ nsfw Boolean @default(false)
altText String?
description String?
diff --git a/src/actions/images/updateImage.ts b/src/actions/images/updateImage.ts
index 21013b9..a3057b2 100644
--- a/src/actions/images/updateImage.ts
+++ b/src/actions/images/updateImage.ts
@@ -17,10 +17,11 @@ export async function updateImage(
imageName,
originalFile,
uploadDate,
+ nsfw,
altText,
description,
fileType,
- imageData,
+ source,
creationMonth,
creationYear,
fileSize,
@@ -37,10 +38,11 @@ export async function updateImage(
imageName,
originalFile,
uploadDate,
+ nsfw,
altText,
description,
fileType,
- imageData,
+ source,
creationMonth,
creationYear,
fileSize,
diff --git a/src/app/colors/[id]/page.tsx b/src/app/colors/[id]/page.tsx
new file mode 100644
index 0000000..f5adf66
--- /dev/null
+++ b/src/app/colors/[id]/page.tsx
@@ -0,0 +1,30 @@
+import DisplayColor from "@/components/colors/single/DisplayColor";
+import prisma from "@/lib/prisma";
+
+export default async function ColorsPage({ params }: { params: { id: string } }) {
+ const { id } = await params;
+
+ const colorData = await prisma.color.findUnique({
+ where: {
+ id,
+ },
+ include: {
+ images: {
+ include: {
+ image: {
+ include: {
+ variants: { where: { type: "thumbnail" } }
+ }
+ }
+ }
+ }
+ }
+ });
+
+ return (
+
+
Show color
+ {colorData ? : 'Color not found...'}
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/colors/page.tsx b/src/app/colors/page.tsx
new file mode 100644
index 0000000..177784b
--- /dev/null
+++ b/src/app/colors/page.tsx
@@ -0,0 +1,22 @@
+import ListColors from "@/components/colors/list/ListColors";
+import prisma from "@/lib/prisma";
+
+export default async function ColorsPage() {
+ const colors = await prisma.color.findMany(
+ {
+ orderBy: { name: "asc" },
+ include: {
+ images: { select: { id: true } }
+ }
+ }
+ );
+
+ return (
+
+
+
Colors
+
+ {colors.length > 0 ?
:
No colors found.
}
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/extract/[id]/page.tsx b/src/app/extract/[id]/page.tsx
new file mode 100644
index 0000000..fa490a8
--- /dev/null
+++ b/src/app/extract/[id]/page.tsx
@@ -0,0 +1,30 @@
+import DisplayExtractColor from "@/components/extract/single/DisplayExtractColor";
+import prisma from "@/lib/prisma";
+
+export default async function ColorsPage({ params }: { params: { id: string } }) {
+ const { id } = await params;
+
+ const colorData = await prisma.extractColor.findUnique({
+ where: {
+ id,
+ },
+ include: {
+ images: {
+ include: {
+ image: {
+ include: {
+ variants: { where: { type: "thumbnail" } }
+ }
+ }
+ }
+ }
+ }
+ });
+
+ return (
+
+
Show color
+ {colorData ? : 'Color not found...'}
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/extract/page.tsx b/src/app/extract/page.tsx
new file mode 100644
index 0000000..313b5bb
--- /dev/null
+++ b/src/app/extract/page.tsx
@@ -0,0 +1,22 @@
+import ListExtractColors from "@/components/extract/list/ListExtractColors";
+import prisma from "@/lib/prisma";
+
+export default async function ExtractColorsPage() {
+ const extractColors = await prisma.extractColor.findMany(
+ {
+ orderBy: { name: "asc" },
+ include: {
+ images: { select: { id: true } }
+ }
+ }
+ );
+
+ return (
+
+
+
Extract colors
+
+ {extractColors.length > 0 ?
:
No colors found.
}
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/images/page.tsx b/src/app/images/page.tsx
index 49aead4..c081641 100644
--- a/src/app/images/page.tsx
+++ b/src/app/images/page.tsx
@@ -4,11 +4,11 @@ import { PlusCircleIcon } from "lucide-react";
import Link from "next/link";
export default async function ImagesPage() {
- const images = await prisma.image.findMany({ orderBy: { createdAt: "asc" } });
+ const images = await prisma.image.findMany({ orderBy: { imageName: "asc" } });
return (
-
+
Images
Upload new image
diff --git a/src/components/colors/list/ListColors.tsx b/src/components/colors/list/ListColors.tsx
new file mode 100644
index 0000000..bf5dd09
--- /dev/null
+++ b/src/components/colors/list/ListColors.tsx
@@ -0,0 +1,35 @@
+import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
+import { Color } from "@/generated/prisma";
+import Link from "next/link";
+
+type ColorsWithItems = Color & {
+ images: { id: string }[]
+}
+
+export default function ListColors({ colors }: { colors: ColorsWithItems[] }) {
+ return (
+
+ {colors.map((col) => (
+
+
+
+ {col.name}
+
+
+
+
+
+ Used by {col.images.length} image{col.images.length !== 1 ? "s" : ""}
+
+
+
+ ))}
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/colors/single/DisplayColor.tsx b/src/components/colors/single/DisplayColor.tsx
new file mode 100644
index 0000000..7fe5fae
--- /dev/null
+++ b/src/components/colors/single/DisplayColor.tsx
@@ -0,0 +1,41 @@
+
+import ImageColorCard from "@/components/images/ImageColorCard";
+import { Color, Image, ImageColor, ImageVariant } from "@/generated/prisma";
+
+type ColorWithItems = Color & {
+ images: (ImageColor & {
+ image: Image & {
+ variants: ImageVariant[]
+ }
+ })[]
+}
+
+export default function DisplayColor({ color }: { color: ColorWithItems }) {
+
+
+ return (
+
+
+
{color.name}
+
+
+
+
+
+
Used by {color.images.length} image(s)
+
+ {color.images.map((image) => (
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/extract/list/ListExtractColors.tsx b/src/components/extract/list/ListExtractColors.tsx
new file mode 100644
index 0000000..75218ae
--- /dev/null
+++ b/src/components/extract/list/ListExtractColors.tsx
@@ -0,0 +1,35 @@
+import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
+import { ExtractColor } from "@/generated/prisma";
+import Link from "next/link";
+
+type ColorsWithItems = ExtractColor & {
+ images: { id: string }[]
+}
+
+export default function ListExtractColors({ colors }: { colors: ColorsWithItems[] }) {
+ return (
+
+ {colors.map((col) => (
+
+
+
+ {col.name}
+
+
+
+
+
+ Used by {col.images.length} image{col.images.length !== 1 ? "s" : ""}
+
+
+
+ ))}
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/extract/single/DisplayExtractColor.tsx b/src/components/extract/single/DisplayExtractColor.tsx
new file mode 100644
index 0000000..b029d98
--- /dev/null
+++ b/src/components/extract/single/DisplayExtractColor.tsx
@@ -0,0 +1,41 @@
+
+import ImageExtractColorCard from "@/components/images/ImageExtractColorCard";
+import { ExtractColor, Image, ImageExtractColor, ImageVariant } from "@/generated/prisma";
+
+type ColorWithItems = ExtractColor & {
+ images: (ImageExtractColor & {
+ image: Image & {
+ variants: ImageVariant[]
+ }
+ })[]
+}
+
+export default function DisplayExtractColor({ color }: { color: ColorWithItems }) {
+
+
+ return (
+
+
+
{color.name}
+
+
+
+
+
+
Used by {color.images.length} image(s)
+
+ {color.images.map((image) => (
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/global/TopNav.tsx b/src/components/global/TopNav.tsx
index f1f3363..78af5ef 100644
--- a/src/components/global/TopNav.tsx
+++ b/src/components/global/TopNav.tsx
@@ -47,6 +47,11 @@ export default function TopNav() {
Colors
+
+
+ Extract-Colors
+
+
Images
diff --git a/src/components/images/ImageColorCard.tsx b/src/components/images/ImageColorCard.tsx
new file mode 100644
index 0000000..dadfcfb
--- /dev/null
+++ b/src/components/images/ImageColorCard.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import { Image, ImageColor, ImageVariant } from "@/generated/prisma";
+import ImageComponent from "next/image";
+import Link from "next/link";
+
+type ImagePaletteWithItems = {
+ image: ImageColor & {
+ image: Image & {
+ variants: ImageVariant[];
+ }
+ };
+};
+
+export default function ImageColorCard({ image }: ImagePaletteWithItems) {
+ const thumbnail = image.image.variants.find((v) => v.type === "thumbnail");
+
+ return (
+
+ {thumbnail?.s3Key ? (
+
+ ) : (
+
+ No Thumbnail
+
+ )}
+ {image.image.imageName} ({image.type})
+
+ );
+}
diff --git a/src/components/images/ImageExtractColorCard.tsx b/src/components/images/ImageExtractColorCard.tsx
new file mode 100644
index 0000000..3338579
--- /dev/null
+++ b/src/components/images/ImageExtractColorCard.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import { Image, ImageExtractColor, ImageVariant } from "@/generated/prisma";
+import ImageComponent from "next/image";
+import Link from "next/link";
+
+type ImagePaletteWithItems = {
+ image: ImageExtractColor & {
+ image: Image & {
+ variants: ImageVariant[];
+ }
+ };
+};
+
+export default function ImageExtractColorCard({ image }: ImagePaletteWithItems) {
+ const thumbnail = image.image.variants.find((v) => v.type === "thumbnail");
+
+ return (
+
+ {thumbnail?.s3Key ? (
+
+ ) : (
+
+ No Thumbnail
+
+ )}
+ {image.image.imageName} ({image.type})
+
+ );
+}
diff --git a/src/components/images/edit/EditImageForm.tsx b/src/components/images/edit/EditImageForm.tsx
index a160b41..7f1121c 100644
--- a/src/components/images/edit/EditImageForm.tsx
+++ b/src/components/images/edit/EditImageForm.tsx
@@ -3,11 +3,12 @@
import { updateImage } from "@/actions/images/updateImage";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
+import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import MultipleSelector from "@/components/ui/multiselect";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { Album, Artist, Category, Color, ColorPalette, ColorPaletteItem, ExtractColor, Image, ImageColor, ImageExtractColor, ImageMetadata, ImagePalette, ImageStats, ImageVariant, Tag } from "@/generated/prisma";
import { cn } from "@/lib/utils";
@@ -68,6 +69,7 @@ export default function EditImageForm({ image, albums, artists, categories, tags
imageName: image.imageName,
originalFile: image.originalFile,
uploadDate: image.uploadDate,
+ nsfw: image.nsfw ?? false,
altText: image.altText || "",
description: image.description || "",
@@ -82,7 +84,7 @@ export default function EditImageForm({ image, albums, artists, categories, tags
albumId: image.album?.id || undefined,
tagIds: image.tags?.map(tag => tag.id) ?? [],
categoryIds: image.categories?.map(cat => cat.id) ?? [],
- },
+ }
})
// const watchCreationDate = form.watch("creationDate");
@@ -167,6 +169,22 @@ export default function EditImageForm({ image, albums, artists, categories, tags
)}
/>
+ (
+
+
+ NSFW
+ This image contains sensitive or adult content.
+
+
+
+
+
+ )}
+ />
+
+
{images.map((image) => (
-
-
-
- {image.imageName}
-
-
-
-
-
-
+
+
+
+
+ {image.imageName}
+
+
+
+
+
+
+
))}
);
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
new file mode 100644
index 0000000..6a2b524
--- /dev/null
+++ b/src/components/ui/switch.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as SwitchPrimitive from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+function Switch({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export { Switch }
diff --git a/src/schemas/images/imageSchema.ts b/src/schemas/images/imageSchema.ts
index 14d4646..07d62d0 100644
--- a/src/schemas/images/imageSchema.ts
+++ b/src/schemas/images/imageSchema.ts
@@ -14,6 +14,7 @@ export const imageSchema = z.object({
imageName: z.string().min(1, "Image name is required"),
originalFile: z.string().min(1, "Original file is required"),
uploadDate: z.date(),
+ nsfw: z.boolean(),
altText: z.string().optional(),
description: z.string().optional(),