chore: run prettier

This commit is contained in:
Ashhhleyyy 2022-07-25 18:31:20 +01:00
parent d49926e55a
commit 0ae9d6efd8
Signed by: ash
GPG key ID: 83B789081A0878FB
16 changed files with 577 additions and 375 deletions

View file

@ -1,16 +1,43 @@
import { AnchorHTMLAttributes, ButtonHTMLAttributes, DetailedHTMLProps, RefAttributes } from "react";
import {
AnchorHTMLAttributes,
ButtonHTMLAttributes,
DetailedHTMLProps,
RefAttributes,
} from 'react';
import Link, { LinkProps } from 'next/link';
export default function Button({ className, ...props }: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>) {
return <button className={'bg-slate-600 text-slate-50 p-1 rounded shadow shadow-slate-900 hover:bg-slate-500 transition-colors ' + (className || '')} {...props} />
export default function Button({
className,
...props
}: DetailedHTMLProps<
ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>) {
return (
<button
className={
'bg-slate-600 text-slate-50 p-1 rounded shadow shadow-slate-900 hover:bg-slate-500 transition-colors ' +
(className || '')
}
{...props}
/>
);
}
type LinkButtonProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps> & LinkProps & {
children?: React.ReactNode;
} & RefAttributes<HTMLAnchorElement>;
type LinkButtonProps = Omit<
AnchorHTMLAttributes<HTMLAnchorElement>,
keyof LinkProps
> &
LinkProps & {
children?: React.ReactNode;
} & RefAttributes<HTMLAnchorElement>;
export function LinkButton({ className, children, ...props }: LinkButtonProps) {
return <Link {...props}>
<a className={'text-blue-200 hover:underline ' + (className || '')}>{children}</a>
</Link>
return (
<Link {...props}>
<a className={'text-blue-200 hover:underline ' + (className || '')}>
{children}
</a>
</Link>
);
}

View file

@ -1,4 +1,4 @@
import { signIn, signOut, useSession } from "next-auth/react";
import { signIn, signOut, useSession } from 'next-auth/react';
export default function NavBar() {
const { data, status } = useSession();
@ -6,11 +6,15 @@ export default function NavBar() {
return (
<nav className='flex flex-row h-12 bg-gray-900 justify-end items-center p-4 gap-1 shadow mb-4'>
{status === 'loading' && <>...</>}
{status === 'unauthenticated' && <button onClick={() => signIn('keycloak')}>Log in</button>}
{status === 'authenticated' && <>
<span>{data?.user?.name}</span>
<button onClick={() => signOut()}>(Log out)</button>
</>}
{status === 'unauthenticated' && (
<button onClick={() => signIn('keycloak')}>Log in</button>
)}
{status === 'authenticated' && (
<>
<span>{data?.user?.name}</span>
<button onClick={() => signOut()}>(Log out)</button>
</>
)}
</nav>
)
);
}

View file

@ -1,12 +1,37 @@
import { DetailedHTMLProps, InputHTMLAttributes, TextareaHTMLAttributes } from "react";
import {
DetailedHTMLProps,
InputHTMLAttributes,
TextareaHTMLAttributes,
} from 'react';
type Props = ({ multiline: true } & DetailedHTMLProps<TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>)
| ({ multiline: false | undefined } & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>);
type Props =
| ({ multiline: true } & DetailedHTMLProps<
TextareaHTMLAttributes<HTMLTextAreaElement>,
HTMLTextAreaElement
>)
| ({ multiline: false | undefined } & DetailedHTMLProps<
InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>);
export default function TextInput({ multiline, className, ...props }: Props) {
if (multiline) {
return <textarea className={'bg-slate-600 p-1 overflow-auto rounded ' + className} {...props as any} />
return (
<textarea
className={
'bg-slate-600 p-1 overflow-auto rounded ' + className
}
{...(props as any)}
/>
);
} else {
return <input className={'bg-slate-600 p-1 overflow-auto rounded ' + className} {...props as any} />
return (
<input
className={
'bg-slate-600 p-1 overflow-auto rounded ' + className
}
{...(props as any)}
/>
);
}
}

View file

@ -3,9 +3,7 @@ const nextConfig = {
reactStrictMode: true,
swcMinify: true,
images: {
domains: [
'img.comicfury.com'
],
domains: ['img.comicfury.com'],
},
};

View file

@ -4,12 +4,14 @@ import { SessionProvider } from 'next-auth/react';
import NavBar from '../components/NavBar';
function MyApp({ Component, pageProps }: AppProps) {
return <>
<SessionProvider>
<NavBar />
<Component {...pageProps} />
</SessionProvider>
</>;
return (
<>
<SessionProvider>
<NavBar />
<Component {...pageProps} />
</SessionProvider>
</>
);
}
export default MyApp;

View file

@ -1,6 +1,6 @@
import NextAuth, { NextAuthOptions } from "next-auth";
import Keycloak from "next-auth/providers/keycloak";
import process from "process";
import NextAuth, { NextAuthOptions } from 'next-auth';
import Keycloak from 'next-auth/providers/keycloak';
import process from 'process';
export const authOptions: NextAuthOptions = {
providers: [

View file

@ -1,32 +1,39 @@
import { ComicBubble, Prisma } from "@prisma/client";
import { NextApiRequest, NextApiResponse } from "next";
import { unstable_getServerSession } from "next-auth";
import { ComicBubble, Prisma } from '@prisma/client';
import { NextApiRequest, NextApiResponse } from 'next';
import { unstable_getServerSession } from 'next-auth';
import { z } from 'zod';
import { prisma } from "../../src/db";
import { authOptions } from "./auth/[...nextauth]";
import { prisma } from '../../src/db';
import { authOptions } from './auth/[...nextauth]';
const PageBubblesSchema = z.object({
bubbles: z.array(z.object({
area: z.object({
x: z.number(),
y: z.number(),
width: z.number(),
height: z.number(),
}),
character: z.number(),
index: z.number(),
text: z.string(),
})),
bubbles: z.array(
z.object({
area: z.object({
x: z.number(),
y: z.number(),
width: z.number(),
height: z.number(),
}),
character: z.number(),
index: z.number(),
text: z.string(),
})
),
comicId: z.number(),
pageId: z.number(),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'PUT') return res.status(405).json({ error: 'method not allowed' });
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'PUT')
return res.status(405).json({ error: 'method not allowed' });
const session = await unstable_getServerSession(req, res, authOptions);
if (!session) return res.status(401).json({ error: 'authentication required' });
if (!session)
return res.status(401).json({ error: 'authentication required' });
const data = PageBubblesSchema.parse(req.body);
const page = await prisma.comicPage.findFirst({
@ -46,7 +53,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const char = await prisma.comicCharacter.findFirst({
where: {
id: bubble.character,
}
},
});
if (!char) {
return res.status(400).json({ message: 'bad request' });
@ -79,11 +86,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
areaHeight: bubble.area.height,
characterId: bubble.character,
content: bubble.text,
}
},
});
ops.push(op);
}
await prisma.$transaction(ops);
res.status(201).json({ });
res.status(201).json({});
}

View file

@ -1,9 +1,9 @@
import { Comic } from "@prisma/client";
import { GetServerSideProps } from "next";
import Head from "next/head";
import { ParsedUrlQuery } from "querystring";
import { LinkButton } from "../../../components/Button";
import { prisma } from "../../../src/db";
import { Comic } from '@prisma/client';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
import { ParsedUrlQuery } from 'querystring';
import { LinkButton } from '../../../components/Button';
import { prisma } from '../../../src/db';
interface Params extends ParsedUrlQuery {
comic: string;
@ -14,47 +14,56 @@ interface Props {
completion: {
completePages: number;
totalPages: number;
}
};
}
export default function ComicPage({ comic, completion }: Props) {
return <main className='p-4'>
<Head>
<title>{`${comic.title}`}</title>
</Head>
<h1 className='text-4xl pb-2'>
{comic.title}
</h1>
return (
<main className='p-4'>
<Head>
<title>{`${comic.title}`}</title>
</Head>
<h1 className='text-4xl pb-2'>{comic.title}</h1>
<section>
<p>
So far, a total of {completion.completePages} out of {completion.totalPages} pages have been transcribed!
</p>
</section>
<section>
<p>
So far, a total of {completion.completePages} out of{' '}
{completion.totalPages} pages have been transcribed!
</p>
</section>
<nav>
<ul>
<li>
<LinkButton href={`/comic/${comic.slug}/transcribe/random`}>
Random page
</LinkButton>
</li>
<li>
<LinkButton href={`/comic/${comic.slug}/transcribe/random?completed=true`}>
Random page (complete)
</LinkButton>
</li>
<li>
<LinkButton href={`/comic/${comic.slug}/transcribe/random?completed=false`}>
Random page (incomplete)
</LinkButton>
</li>
</ul>
</nav>
</main>
<nav>
<ul>
<li>
<LinkButton
href={`/comic/${comic.slug}/transcribe/random`}
>
Random page
</LinkButton>
</li>
<li>
<LinkButton
href={`/comic/${comic.slug}/transcribe/random?completed=true`}
>
Random page (complete)
</LinkButton>
</li>
<li>
<LinkButton
href={`/comic/${comic.slug}/transcribe/random?completed=false`}
>
Random page (incomplete)
</LinkButton>
</li>
</ul>
</nav>
</main>
);
}
export const getServerSideProps: GetServerSideProps<Props, Params> = async ({ params }) => {
export const getServerSideProps: GetServerSideProps<Props, Params> = async ({
params,
}) => {
const { comic: comicId } = params!;
const comic = await prisma.comic.findFirst({
@ -83,8 +92,9 @@ export const getServerSideProps: GetServerSideProps<Props, Params> = async ({ pa
comic,
completion: {
totalPages: pages.length,
completePages: pages.filter(page => page._count.bubbles !== 0).length,
}
}
}
}
completePages: pages.filter((page) => page._count.bubbles !== 0)
.length,
},
},
};
};

View file

@ -1,15 +1,19 @@
import { AreaSelector, IArea, IAreaRendererProps } from "@bmunozg/react-image-area";
import { GetServerSideProps } from "next";
import Head from "next/head";
import {
AreaSelector,
IArea,
IAreaRendererProps,
} from '@bmunozg/react-image-area';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
import Image from 'next/image';
import { ParsedUrlQuery } from "querystring";
import { useCallback, useMemo, useState } from "react";
import { ParsedUrlQuery } from 'querystring';
import { useCallback, useMemo, useState } from 'react';
import Select from 'react-select';
import Button, { LinkButton } from "../../../../components/Button";
import TextInput from "../../../../components/TextInput";
import { prisma } from "../../../../src/db";
import { performOcr } from "../../../../src/ocr";
import { percentify, randomColour, unPercentify } from "../../../../src/utils";
import Button, { LinkButton } from '../../../../components/Button';
import TextInput from '../../../../components/TextInput';
import { prisma } from '../../../../src/db';
import { performOcr } from '../../../../src/ocr';
import { percentify, randomColour, unPercentify } from '../../../../src/utils';
interface Params extends ParsedUrlQuery {
comic: string;
@ -44,11 +48,16 @@ interface Region {
const SelectedArea = (props: IAreaRendererProps) => {
if (!props.isChanging) {
return <div key={props.areaNumber} className='text-slate-900 font-bold bg-slate-50 opacity-90 text-center'>
{props.areaNumber}
</div>
return (
<div
key={props.areaNumber}
className='text-slate-900 font-bold bg-slate-50 opacity-90 text-center'
>
{props.areaNumber}
</div>
);
}
}
};
export default function TranscribePage(props: Props) {
const [regions, setRegions] = useState<Region[]>(props.regions);
@ -59,25 +68,29 @@ export default function TranscribePage(props: Props) {
const extractText = useCallback(() => {
if (image && regions.length > 0) {
const promises = regions.map(region => performOcr(image, region.area));
Promise.all(promises).then(texts => {
setRegions(regions.map((region, i) => {
return {
...region,
text: texts[i],
};
}));
const promises = regions.map((region) =>
performOcr(image, region.area)
);
Promise.all(promises).then((texts) => {
setRegions(
regions.map((region, i) => {
return {
...region,
text: texts[i],
};
})
);
});
}
}, [regions, setRegions, image]);
const characters = useMemo(() => {
return props.characters.map(character => {
return props.characters.map((character) => {
return {
value: character.id,
label: character.shortName,
};
})
});
}, [props.characters]);
async function submitBubbles() {
@ -88,24 +101,39 @@ export default function TranscribePage(props: Props) {
},
body: JSON.stringify({
bubbles: regions.map((region, i) => {
const area = region.area.unit === 'px' ? {
x: region.area.x,
y: region.area.y,
width: region.area.width,
height: region.area.height,
} : {
x: unPercentify(props.page.width, region.area.x),
y: unPercentify(props.page.height, region.area.y),
width: unPercentify(props.page.width, region.area.width),
height: unPercentify(props.page.height, region.area.height),
};
const area =
region.area.unit === 'px'
? {
x: region.area.x,
y: region.area.y,
width: region.area.width,
height: region.area.height,
}
: {
x: unPercentify(
props.page.width,
region.area.x
),
y: unPercentify(
props.page.height,
region.area.y
),
width: unPercentify(
props.page.width,
region.area.width
),
height: unPercentify(
props.page.height,
region.area.height
),
};
return {
area,
character: region.character?.value ?? -1,
index: i,
text: region.text,
}
};
}),
comicId: props.page.comic.id,
pageId: props.page.id,
@ -118,162 +146,249 @@ export default function TranscribePage(props: Props) {
}
}
return <main className='p-4'>
<Head>
<title>{`${props.page.title} - ${props.page.comic.title}`}</title>
</Head>
return (
<main className='p-4'>
<Head>
<title>{`${props.page.title} - ${props.page.comic.title}`}</title>
</Head>
<div className='flex flex-row w-full gap-4'>
<section className='h-full max-w-1/2'>
<header className='mb-4'>
<nav className='flex flex-row w-full'>
<LinkButton className='flex-1 w-full' href={{
pathname: '/comic/[comic]',
query: {
comic: props.page.comic.slug,
}
}}>
&larr; Back to {props.page.comic.title}
</LinkButton>
</nav>
<h1 className='text-4xl pb-2'>{props.page.title}</h1>
<nav className='flex flex-row w-full'>
{props.page.id > 1 && <LinkButton className='flex-1 w-full text-center' href={{
pathname: '/comic/[comic]/transcribe/[page]',
query: {
comic: props.page.comic.slug,
page: props.page.id - 1,
}
}}>
&larr; Previous page
</LinkButton>}
<LinkButton className='flex-1 w-full text-center' href={{
pathname: '/comic/[comic]/transcribe/random',
query: {
comic: props.page.comic.slug,
}
}}>
Random page
</LinkButton>
<LinkButton className='flex-1 w-full text-center' href={{
pathname: '/comic/[comic]/transcribe/[page]',
query: {
comic: props.page.comic.slug,
page: props.page.id + 1,
}
}}>
Next page &rarr;
</LinkButton>
</nav>
</header>
<AreaSelector
areas={regions.map(r => r.area)}
onChange={areas => !lockAreas && setRegions(areas.map((area, i) => {
if (i < regions.length) {
return {
...regions[i],
area,
};
} else {
return {
area,
character: null,
index: i,
text: '',
};
}
}))}
globalAreaStyle={{
border: `1.5px dashed ${randomColour(props.page.comic.id, 0.3, .49, 0.5)}`,
backgroundColor: randomColour(props.page.comic.id, undefined, undefined, 0.5),
}}
customAreaRenderer={indicateNumbers ? SelectedArea : undefined}
unit="percentage"
>
<Image crossOrigin="" src={props.page.imageUrl} alt={props.page.title} width={props.page.width} height={props.page.height} onLoad={e => setImage(e.currentTarget)} />
</AreaSelector>
</section>
<section className='flex-1'>
<div>
<Button onClick={extractText}>Extract text</Button>
<br />
<label htmlFor='indicate-numbers'>
<input id='indicate-numbers' type='checkbox' value={indicateNumbers.toString()} onChange={e => setIndicateNumbers(e.target.checked)} />
Show selection numbers?
</label>
<br />
<label htmlFor='lock-areas'>
<input id='lock-areas' type='checkbox' value={lockAreas.toString()} onChange={e => setLockAreas(e.target.checked)} />
Lock areas
</label>
</div>
<hr className='border-slate-600 my-2' />
<div>
{selectedBubble === -1 && <p>
<em>Click a bubble to edit</em>
</p>}
{selectedBubble !== -1 && <>
<TextInput multiline className='w-full h-24'
value={regions[selectedBubble].text} onChange={(e) => {
const newRegions = regions.slice();
newRegions[selectedBubble].text = e.target.value;
setRegions(newRegions);
}} />
<Select className='text-slate-900' onChange={(v) => {
const newRegions = regions.slice();
newRegions[selectedBubble].character = v;
setRegions(newRegions);
}} options={characters} value={regions[selectedBubble].character} />
<Button className='mt-2 w-full' onClick={() => {
const newRegions = regions.filter((_, i) => i !== selectedBubble);
setRegions(newRegions);
setSelectedBubble(-1);
}}>
Remove
</Button>
</>}
</div>
<hr className='border-slate-600 my-2' />
<div className='grid grid-cols-2 gap-2'>
{regions.map((region, i) => {
return <div
key={i}
onClick={() => setSelectedBubble(selectedBubble === i ? -1 : i)}
className={'border-slate-600 border-2 rounded p-2 ' + (i === selectedBubble ? 'bg-teal-900 border-teal-700' : '')}
<div className='flex flex-row w-full gap-4'>
<section className='h-full max-w-1/2'>
<header className='mb-4'>
<nav className='flex flex-row w-full'>
<LinkButton
className='flex-1 w-full'
href={{
pathname: '/comic/[comic]',
query: {
comic: props.page.comic.slug,
},
}}
>
<h3>Bubble {i + 1}</h3>
&larr; Back to {props.page.comic.title}
</LinkButton>
</nav>
<h1 className='text-4xl pb-2'>{props.page.title}</h1>
<nav className='flex flex-row w-full'>
{props.page.id > 1 && (
<LinkButton
className='flex-1 w-full text-center'
href={{
pathname:
'/comic/[comic]/transcribe/[page]',
query: {
comic: props.page.comic.slug,
page: props.page.id - 1,
},
}}
>
&larr; Previous page
</LinkButton>
)}
<LinkButton
className='flex-1 w-full text-center'
href={{
pathname:
'/comic/[comic]/transcribe/random',
query: {
comic: props.page.comic.slug,
},
}}
>
Random page
</LinkButton>
<LinkButton
className='flex-1 w-full text-center'
href={{
pathname:
'/comic/[comic]/transcribe/[page]',
query: {
comic: props.page.comic.slug,
page: props.page.id + 1,
},
}}
>
Next page &rarr;
</LinkButton>
</nav>
</header>
<AreaSelector
areas={regions.map((r) => r.area)}
onChange={(areas) =>
!lockAreas &&
setRegions(
areas.map((area, i) => {
if (i < regions.length) {
return {
...regions[i],
area,
};
} else {
return {
area,
character: null,
index: i,
text: '',
};
}
})
)
}
globalAreaStyle={{
border: `1.5px dashed ${randomColour(
props.page.comic.id,
0.3,
0.49,
0.5
)}`,
backgroundColor: randomColour(
props.page.comic.id,
undefined,
undefined,
0.5
),
}}
customAreaRenderer={
indicateNumbers ? SelectedArea : undefined
}
unit='percentage'
>
<Image
crossOrigin=''
src={props.page.imageUrl}
alt={props.page.title}
width={props.page.width}
height={props.page.height}
onLoad={(e) => setImage(e.currentTarget)}
/>
</AreaSelector>
</section>
<section className='flex-1'>
<div>
<Button onClick={extractText}>Extract text</Button>
<br />
<label htmlFor='indicate-numbers'>
<input
id='indicate-numbers'
type='checkbox'
value={indicateNumbers.toString()}
onChange={(e) =>
setIndicateNumbers(e.target.checked)
}
/>
Show selection numbers?
</label>
<br />
<label htmlFor='lock-areas'>
<input
id='lock-areas'
type='checkbox'
value={lockAreas.toString()}
onChange={(e) => setLockAreas(e.target.checked)}
/>
Lock areas
</label>
</div>
<hr className='border-slate-600 my-2' />
<div>
{selectedBubble === -1 && (
<p>
<b>{region.character?.label}</b>: {region.text || '...'}
<em>Click a bubble to edit</em>
</p>
</div>;
})}
</div>
)}
<hr className='border-slate-600 my-2' />
{selectedBubble !== -1 && (
<>
<TextInput
multiline
className='w-full h-24'
value={regions[selectedBubble].text}
onChange={(e) => {
const newRegions = regions.slice();
newRegions[selectedBubble].text =
e.target.value;
setRegions(newRegions);
}}
/>
<div>
<Button onClick={submitBubbles}>
Save!
</Button>
</div>
</section>
</div>
</main>
<Select
className='text-slate-900'
onChange={(v) => {
const newRegions = regions.slice();
newRegions[selectedBubble].character =
v;
setRegions(newRegions);
}}
options={characters}
value={regions[selectedBubble].character}
/>
<Button
className='mt-2 w-full'
onClick={() => {
const newRegions = regions.filter(
(_, i) => i !== selectedBubble
);
setRegions(newRegions);
setSelectedBubble(-1);
}}
>
Remove
</Button>
</>
)}
</div>
<hr className='border-slate-600 my-2' />
<div className='grid grid-cols-2 gap-2'>
{regions.map((region, i) => {
return (
<div
key={i}
onClick={() =>
setSelectedBubble(
selectedBubble === i ? -1 : i
)
}
className={
'border-slate-600 border-2 rounded p-2 ' +
(i === selectedBubble
? 'bg-teal-900 border-teal-700'
: '')
}
>
<h3>Bubble {i + 1}</h3>
<p>
<b>{region.character?.label}</b>:{' '}
{region.text || '...'}
</p>
</div>
);
})}
</div>
<hr className='border-slate-600 my-2' />
<div>
<Button onClick={submitBubbles}>Save!</Button>
</div>
</section>
</div>
</main>
);
}
export const getServerSideProps: GetServerSideProps<Props, Params> = async ({ params }) => {
export const getServerSideProps: GetServerSideProps<Props, Params> = async ({
params,
}) => {
const { comic: comicId, page: id } = params!;
const pageId = parseInt(id);
if (isNaN(pageId)) return { notFound: true };
@ -329,8 +444,8 @@ export const getServerSideProps: GetServerSideProps<Props, Params> = async ({ pa
select: {
id: true,
shortName: true,
}
}
},
},
},
});
@ -339,7 +454,7 @@ export const getServerSideProps: GetServerSideProps<Props, Params> = async ({ pa
page,
characters,
key: `${page.comic.slug}-${page.id}`,
regions: bubbles.map(bubble => {
regions: bubbles.map((bubble) => {
return {
character: {
value: bubble.character.id,
@ -354,8 +469,8 @@ export const getServerSideProps: GetServerSideProps<Props, Params> = async ({ pa
},
index: bubble.idx,
text: bubble.content,
}
})
};
}),
},
};
}
};

View file

@ -1,43 +1,46 @@
import { GetServerSideProps } from "next";
import { ParsedUrlQuery } from "querystring";
import { prisma } from "../../../../src/db";
import { GetServerSideProps } from 'next';
import { ParsedUrlQuery } from 'querystring';
import { prisma } from '../../../../src/db';
interface Params extends ParsedUrlQuery {
comic: string;
}
export default function RandomPage() {
return <>
Please wait...
</>
return <>Please wait...</>;
}
export const getServerSideProps: GetServerSideProps<{}, Params> = async ({ params, query }) => {
export const getServerSideProps: GetServerSideProps<{}, Params> = async ({
params,
query,
}) => {
const { comic: comicSlug } = params!;
const pages = await prisma.comicPage.findMany({
where: {
comic: {
slug: comicSlug,
const pages = await prisma.comicPage
.findMany({
where: {
comic: {
slug: comicSlug,
},
},
},
select: {
id: true,
_count: {
select: {
bubbles: true,
}
select: {
id: true,
_count: {
select: {
bubbles: true,
},
},
},
})
.then((pages) => {
if (query.completed === 'true') {
return pages.filter((page) => page._count.bubbles !== 0);
} else if (query.completed === 'false') {
return pages.filter((page) => page._count.bubbles === 0);
} else {
return pages;
}
}
}).then(pages => {
if (query.completed === 'true') {
return pages.filter(page => page._count.bubbles !== 0);
} else if (query.completed === 'false') {
return pages.filter(page => page._count.bubbles === 0);
} else {
return pages;
}
});
});
console.log(pages.length);
const randomPage = pages[Math.floor(Math.random() * pages.length)];
@ -46,6 +49,6 @@ export const getServerSideProps: GetServerSideProps<{}, Params> = async ({ param
redirect: {
destination: `/comic/${comicSlug}/transcribe/${randomPage.id}`,
permanent: false,
}
}
}
},
};
};

View file

@ -1,6 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View file

@ -21096,7 +21096,7 @@
"longName": "Brett Desrocher",
"shortName": "Brett"
},
{
"longName": "Father Quenton Morrison",
"shortName": "Father Quenton"
@ -21133,7 +21133,7 @@
"Frank Johnson",
"Norman Strongwell",
"Donna Strongwell",
{
"longName": "Heather Coven",
"shortName": "Heather"

View file

@ -34,7 +34,7 @@ async function seedComicRain() {
{
// Chapters not directly related to the story
const SKIP_CHAPTERS = [
0, // Cover page
0, // Cover page
30, // SRS Hiatus
37, // Summer 2018 Hiatus
42, // Quarantine Hiatus 2020
@ -73,14 +73,15 @@ async function seedComicRain() {
{
const ops: Prisma.Prisma__ComicCharacterClient<ComicCharacter>[] = [];
for (const char of rain.characters) {
const character = (typeof char === 'string')
? { longName: char, shortName: char }
: char;
const character =
typeof char === 'string'
? { longName: char, shortName: char }
: char;
const characterData = {
comicId: comic.id,
...character
}
...character,
};
const op = prisma.comicCharacter.upsert({
where: {
longName: character.longName,
@ -95,11 +96,8 @@ async function seedComicRain() {
}
(async function () {
await seedComicRain();
})().catch(e => {
console.error('failed to seed',
e);
})().catch((e) => {
console.error('failed to seed', e);
process.exitCode = 1;
});

View file

@ -1,19 +1,25 @@
import { IArea } from "@bmunozg/react-image-area";
import Tesseract from "tesseract.js";
import { unPercentify } from "./utils";
import { IArea } from '@bmunozg/react-image-area';
import Tesseract from 'tesseract.js';
import { unPercentify } from './utils';
export async function performOcr(image: HTMLImageElement, a: IArea): Promise<string> {
const area = a.unit === 'px' ? {
x: a.x,
y: a.y,
width: a.width,
height: a.height,
} : {
x: unPercentify(image.width, a.x),
y: unPercentify(image.height, a.y),
width: unPercentify(image.width, a.width),
height: unPercentify(image.height, a.height),
};
export async function performOcr(
image: HTMLImageElement,
a: IArea
): Promise<string> {
const area =
a.unit === 'px'
? {
x: a.x,
y: a.y,
width: a.width,
height: a.height,
}
: {
x: unPercentify(image.width, a.x),
y: unPercentify(image.height, a.y),
width: unPercentify(image.width, a.width),
height: unPercentify(image.height, a.height),
};
const canvas = document.createElement('canvas');
canvas.width = area.width;
canvas.height = area.height;

View file

@ -1,10 +1,10 @@
const ONE_OVER_PHI = 0.618033988749895;
function hsvToRgb(h: number, s: number, v: number): string {
const h_i = Math.round(h*6);
const f = h*6 - h_i;
const h_i = Math.round(h * 6);
const f = h * 6 - h_i;
const p = v * (1 - s);
const q = v * (1 - f*s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
let r: number, g: number, b: number;
if (h_i === 0) {
@ -32,15 +32,22 @@ function hsvToRgb(h: number, s: number, v: number): string {
g = p;
b = q;
}
return `${Math.round(r*256)}, ${Math.round(g*256)}, ${Math.round(b*256)}`;
return `${Math.round(r * 256)}, ${Math.round(g * 256)}, ${Math.round(
b * 256
)}`;
}
export function randomColour(index: number, s: number = 0.3, v: number = 0.99, opacity: number = 1) {
export function randomColour(
index: number,
s: number = 0.3,
v: number = 0.99,
opacity: number = 1
) {
// Generated with Math.random()
let h = 0.6220694728604135;
for (var i = 0; i < index; i++) {
h += ONE_OVER_PHI;
h %= 1;
h += ONE_OVER_PHI;
h %= 1;
}
return `rgba(${hsvToRgb(h, s, v)}, ${opacity * 100}%)`;
}

View file

@ -1,15 +1,15 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
maxWidth: {
'1/2': '50%',
},
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
maxWidth: {
'1/2': '50%',
},
},
},
},
plugins: [],
}
plugins: [],
};