feat: add koikatsu (#46)

### Explanation

_No response_

### Issue

_No response_

### Attestations

- [x] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [x] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [x] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [x] I have pinned the dependencies to a specific patch version.

### Style

- [x] I have run the linter and resolved any errors.
- [x] My pull request uses an appropriate title, matching the conventional commit standards.
- [x] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [x] My contribution adds new code, and I have added tests to cover it.
- [x] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [x] All new and existing tests pass locally with my changes.
- [x] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Reviewed-on: https://codeberg.org/nhcarrigan/portfolio/pulls/46
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit is contained in:
Naomi Carrigan 2024-11-18 19:23:27 +00:00 committed by Naomi the Technomancer
parent a919df8f4d
commit 408c4ae16a
11 changed files with 1671 additions and 199 deletions

2
do.env Normal file
View File

@ -0,0 +1,2 @@
DO_ACCESS_KEY="op://Environment Variables - Development/DO Spaces/username"
DO_SECRET_KEY="op://Environment Variables - Development/DO Spaces/credential"

30
koikatsu.ts Normal file
View File

@ -0,0 +1,30 @@
import { ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
import { writeFile } from "fs/promises";
import { join } from "path";
(async () => {
const s3 = new S3Client({
endpoint: "https://sfo3.digitaloceanspaces.com",
region: "sfo3",
credentials: {
accessKeyId: process.env.DO_ACCESS_KEY as string,
secretAccessKey: process.env.DO_SECRET_KEY as string
}
})
const command = new ListObjectsV2Command({
Bucket: "nhcarrigan-cdn",
Prefix: "koikatsu"
})
const { Contents } = await s3.send(command);
const names = Contents?.map((content) => content.Key?.replace("koikatsu/", "")).filter((name) => name) || [];
await writeFile(join(process.cwd(), "src", "config", "Koikatsu.ts"), `/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export const Koikatsu = ${JSON.stringify(names, null, 2)};`, "utf-8");
})()

View File

@ -8,7 +8,8 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint src test --max-warnings 0", "lint": "eslint src test --max-warnings 0",
"test": "vitest run --coverage" "test": "vitest run --coverage",
"koikatsu": "op run --env-file=do.env --no-masking -- tsx koikatsu.ts"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "6.6.0", "@fortawesome/fontawesome-svg-core": "6.6.0",
@ -21,6 +22,7 @@
"react-dom": "18.3.1" "react-dom": "18.3.1"
}, },
"devDependencies": { "devDependencies": {
"@aws-sdk/client-s3": "3.693.0",
"@nhcarrigan/eslint-config": "5.0.0-rc2", "@nhcarrigan/eslint-config": "5.0.0-rc2",
"@nhcarrigan/typescript-config": "4.0.0", "@nhcarrigan/typescript-config": "4.0.0",
"@types/node": "22.8.4", "@types/node": "22.8.4",
@ -31,6 +33,7 @@
"jsdom": "25.0.1", "jsdom": "25.0.1",
"postcss": "8.4.47", "postcss": "8.4.47",
"tailwindcss": "3.4.14", "tailwindcss": "3.4.14",
"tsx": "4.19.2",
"typescript": "5.6.3", "typescript": "5.6.3",
"vitest": "2.1.4" "vitest": "2.1.4"
} }

1702
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

39
src/app/koikatsu/page.tsx Normal file
View File

@ -0,0 +1,39 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { KoikatsuComponent } from "../../components/koikatsu";
import { Rule } from "../../components/rule";
import { Koikatsu } from "../../config/Koikatsu";
import type { JSX } from "react";
/**
* Renders the /games page.
* @returns A React Component.
*/
const KoikatsuPage = (): JSX.Element => {
return (
<main
className="w-[95%] text-center
max-w-4xl m-auto mt-16 mb-16 rounded-lg"
>
<h1 className="text-5xl">{`Koikatsu`}</h1>
<section>
<p className="mb-2">{`Images of Naomi generated in Koikatsu. Note that the list of images is dynamically generated, so we aren't able to provide alt text for these.`}</p>
<Rule />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-y-5">
{Koikatsu.toSorted((a, b) => {
return a.localeCompare(b);
}).map((img) => {
return (
<KoikatsuComponent img={img} key={img} />
);
})}
</div>
</section>
</main>
);
};
export default KoikatsuPage;

View File

@ -0,0 +1,36 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import type { JSX } from "react";
/**
* Renders the view for a koikatsu shot.
* @param props - An object containing the image file name.
* @param props.img - The file name of the image to render.
* @returns A JSX element.
*/
export const KoikatsuComponent = ({ img }: { readonly img: string }):
JSX.Element => {
return (
<div className="w-[300px] border-2
border-solid border-[--foreground] m-auto text-center items-center">
<a
href={`https://cdn.nhcarrigan.com/koikatsu/${img}`}
rel="noreferrer"
target="_blank"
>
<Image
alt="This work is solely decorative. If you'd like alt text,
please reach out in our Discord and we'll be happy to provide it."
className="m-auto object-cover"
height={300}
src={`https://cdn.nhcarrigan.com/koikatsu/${img}`}
width={300}
/>
</a>
</div>
);
};

33
src/config/Koikatsu.ts Normal file
View File

@ -0,0 +1,33 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export const Koikatsu = [
"CharaStudio-2024-10-15-19-14-34-Render.png",
"CharaStudio-2024-10-15-19-15-43-Render.png",
"CharaStudio-2024-10-16-09-30-57-Render.png",
"CharaStudio-2024-10-18-11-54-39-Render.png",
"CharaStudio-2024-10-26-09-13-34-Render.png",
"CharaStudio-2024-10-27-09-44-24-Render.png",
"CharaStudio-2024-10-27-09-45-11-Render.png",
"CharaStudio-2024-10-31-13-20-16-Render.png",
"CharaStudio-2024-10-31-13-27-20-Render.png",
"CharaStudio-2024-11-01-22-20-26-Render.png",
"CharaStudio-2024-11-01-22-21-00-Render.png",
"CharaStudio-2024-11-01-22-22-04-Render.png",
"CharaStudio-2024-11-01-22-22-40-Render.png",
"CharaStudio-2024-11-01-22-23-32-Render.png",
"CharaStudio-2024-11-02-13-41-30-Render.png",
"CharaStudio-2024-11-02-13-45-05-Render.png",
"CharaStudio-2024-11-02-13-46-02-Render.png",
"CharaStudio-2024-11-14-18-12-22-Render.png",
"CharaStudio-2024-11-14-18-13-23-Render.png",
"CharaStudio-2024-11-18-10-22-40-Render.png",
"CharaStudio-2024-11-18-10-23-28-Render.png",
"CharaStudio-2024-11-18-10-24-39-Render.png",
"CharaStudio-2024-11-18-10-25-50-Render.png",
"CharaStudio-2024-11-18-10-27-48-Render.png",
"CharaStudio-2024-11-18-10-29-25-Render.png",
"CharaStudio-2024-11-18-10-30-05-Render.png",
];

View File

@ -25,6 +25,7 @@ export const NavItems = [
{ href: "/play", text: "Play with Naomi" }, { href: "/play", text: "Play with Naomi" },
{ href: "/tech", text: "Technologies" }, { href: "/tech", text: "Technologies" },
{ href: "/contribute", text: "Contribute to our Projects" }, { href: "/contribute", text: "Contribute to our Projects" },
{ href: "/koikatsu", text: "Koikatsu Scenes" },
].sort((a, b) => { ].sort((a, b) => {
return a.text.localeCompare(b.text); return a.text.localeCompare(b.text);
}); });

View File

@ -0,0 +1,17 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { Koikatsu } from "../../src/config/Koikatsu";
describe("koikatsu strings", () => {
it("should be unique", () => {
expect.assertions(1);
const set = new Set(
Koikatsu,
);
expect(set, "are not unique").toHaveLength(Koikatsu.length);
});
});

View File

@ -28,6 +28,7 @@
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"vitest.config.ts" "vitest.config.ts",
"koikatsu.ts"
] ]
} }

View File

@ -14,7 +14,7 @@ export default defineConfig({
branches: 100 branches: 100
}, },
extension: [".ts"], extension: [".ts"],
exclude: [...coverageConfigDefaults.exclude, "tailwind.config.ts"] exclude: [...coverageConfigDefaults.exclude, "tailwind.config.ts", "koikatsu.ts"]
} }
} }
}); });