Skip to content

Commit

Permalink
Merge pull request #109 from AgricolaDevJP/translation-print
Browse files Browse the repository at this point in the history
translation sheet print
  • Loading branch information
Arthur1 authored May 19, 2024
2 parents f74b5de + 7ef942f commit 7d20721
Show file tree
Hide file tree
Showing 17 changed files with 451 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.CardsTableCell {
box-sizing: border-box;
width: 55mm;
height: 40mm;
border: .5mm solid gray;
border-collapse: collapse;
text-align: left;
vertical-align: top;
font-family: 'Noto Sans JP';
font-size: 3.3mm;
overflow: hidden;
}

.CardsTableCellLongText {
font-size: 2.8mm;
}

.CardsTableCellLongLongText {
font-size: 2.5mm;
}

.CardTitle {
font-weight: bold;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import classNames from 'classnames'
import type { FC } from 'react'

import type { CardForPrint } from '@/libs/domain/Card'

import styles from './index.module.scss'

type CardsTranslationPrintListItemProps = Readonly<{
card: CardForPrint
}>

const CardsTranslationPrintListItem: FC<CardsTranslationPrintListItemProps> = ({ card }) => {
const textLength = (card.prerequisite?.length ?? 0) + (card.description?.length ?? 0)
return (
<td
className={classNames({
[styles.CardsTableCell]: true,
[styles.CardsTableCellLongText]: textLength >= 100,
[styles.CardsTableCellLongLongText]: textLength >= 130,
})}
>
<span className={styles.CardTitle}>
[{card.printedID}] {card.nameJa}
</span>
<br />
{card.prerequisite && (
<>
<span>(前提) {card.prerequisite}</span>
<br />
</>
)}
<span>{card.description}</span>
</td>
)
}

export default CardsTranslationPrintListItem
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.CardsTable {
width: 100%;
border: .5mm gray solid;
border-collapse: collapse;
}

.CardsTableRow {
break-inside: avoid-page;
border-collapse: collapse;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { GraphQLClient } from 'graphql-request'
import { type FC, useEffect, useState } from 'react'

import { getSdk } from '@/libs/api/generated'
import { paramsToSearchCondition, searchConditionToWhere } from '@/libs/cards/search'
import type { CardForPrint } from '@/libs/domain/Card'
import type { RevisionKey } from '@/libs/domain/Revision'
import { isNonNullable } from '@/libs/utils/types'

import CardsTranslationPrintListItem from './CardsTranslationPrintListItem'
import styles from './index.module.scss'

type CardsTranslationPrintListProps = Readonly<{
revisionKey: RevisionKey
}>

const CardsTranslationPrintList: FC<CardsTranslationPrintListProps> = ({ revisionKey }) => {
const [cards, setCards] = useState<CardForPrint[]>([])
const [isLoading, setIsLoading] = useState<boolean>(false)
const [isLoaded, setIsLoaded] = useState<boolean>(false)

const params = new URLSearchParams(window.location.search)
const searchCondition = paramsToSearchCondition(params)
const where = searchConditionToWhere(revisionKey, searchCondition)

const client = new GraphQLClient('https://api.db.agricolajp.dev/graphql')
const sdk = getSdk(client)

useEffect(() => {
const fetch = async () => {
if (isLoading) return
setIsLoading(true)
const res = await sdk.GetCardsListForPrint({ where })
const cards = res.cards?.edges?.map(e => e?.node).filter(isNonNullable) ?? []
setCards(cards)
setIsLoading(false)
setIsLoaded(true)
}
fetch()
}, [])

useEffect(() => {
if (isLoaded) {
window.print()
}
}, [isLoaded])

const cardsChunks = cards.reduce(
(acc: CardForPrint[][], _c, i) => (i % 3 ? acc : [...acc, ...[cards.slice(i, i + 3)]]),
[],
)

return (
<>
{isLoading ? (
<p>読込中です。しばらくお待ちください</p>
) : (
<table className={styles.CardsTable}>
<tbody>
{cardsChunks.map(cardsChunk => (
<tr className={styles.CardsTableRow} key={`chunk_${cardsChunk[0].literalID}}`}>
{cardsChunk.map(card => (
<CardsTranslationPrintListItem card={card} key={card.literalID} />
))}
</tr>
))}
</tbody>
</table>
)}
</>
)
}

export default CardsTranslationPrintList
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import {
import { Button, FloatingLabel, Form } from 'react-bootstrap'
import { FaSearch } from 'react-icons/fa'

import type { CardTypeCondition, CardsSearchCondition } from '@/libs/domain/CardsSearchCondition'
import type { DeckSummary } from '@/libs/domain/Deck'
import type { ProductSummary } from '@/libs/domain/Product'

import type { CardTypeCondition, CardsSearchCondition } from '..'

type CardsSearchFormProps = Readonly<{
decks: Readonly<DeckSummary[]>
products: Readonly<ProductSummary[]>
Expand Down
97 changes: 22 additions & 75 deletions src/components/pages/cards/CardsExplorer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,23 @@
import classNames from 'classnames'
import { GraphQLClient } from 'graphql-request'
import { type FC, useCallback, useEffect, useRef, useState } from 'react'
import { Col, Row, Spinner } from 'react-bootstrap'
import { Button, Col, Row, Spinner } from 'react-bootstrap'
import { FaPrint, FaRegCircleCheck } from 'react-icons/fa6'

import Headline2 from '@/components/common/Headline2'
import {
type CardTypeWhereInput,
type CardWhereInput,
type PageInfo,
getSdk,
} from '@/libs/api/generated'
import { type PageInfo, getSdk } from '@/libs/api/generated'
import { paramsToSearchCondition, searchConditionToWhere } from '@/libs/cards/search'
import type { CardSummary } from '@/libs/domain/Card'
import type { CardsSearchCondition } from '@/libs/domain/CardsSearchCondition'
import type { DeckSummary } from '@/libs/domain/Deck'
import type { ProductSummary } from '@/libs/domain/Product'
import type { RevisionKey } from '@/libs/domain/Revision'
import { isNonNullable, unreachable } from '@/libs/utils/types'
import { isNonNullable } from '@/libs/utils/types'

import CardsList from './CardsList'
import CardsSearchForm from './CardsSearchForm'
import styles from './index.module.scss'

const cardTypeConditions = ['occupation', 'minor_improvement', 'major_improvement', 'misc'] as const
export type CardTypeCondition = (typeof cardTypeConditions)[number]

export type CardsSearchCondition = Readonly<{
productID: string | undefined
deckID: string | undefined
cardType: CardTypeCondition | undefined
nameJa: string | undefined
nameEn: string | undefined
description: string | undefined
}>

const paramsToSearchCondition = (params: URLSearchParams): CardsSearchCondition => {
const productID = params.get('productID')
const deckID = params.get('deckID')
const cardType = params.get('cardType')
const nameJa = params.get('nameJa')
const nameEn = params.get('nameEn')
const description = params.get('description')
const isCardType = (cardType: string): cardType is CardTypeCondition =>
cardTypeConditions.some(c => c === cardType)
return {
productID: productID !== null ? productID : undefined,
deckID: deckID !== null ? deckID : undefined,
cardType: cardType !== null && isCardType(cardType) ? cardType : undefined,
nameJa: nameJa !== null ? nameJa : undefined,
nameEn: nameEn !== null ? nameEn : undefined,
description: description !== null ? description : undefined,
}
}

const searchConditionToWhere = (revisionKey: string, searchCondition: CardsSearchCondition) => {
let hasCardTypeWith: CardTypeWhereInput['hasCardsWith']
switch (searchCondition.cardType) {
case 'occupation':
hasCardTypeWith = [{ nameJa: '職業' }]
break
case 'minor_improvement':
hasCardTypeWith = [{ nameJa: '小さい進歩' }]
break
case 'major_improvement':
hasCardTypeWith = [{ nameJa: '大きい進歩' }]
break
case 'misc':
hasCardTypeWith = [{ nameJaNotIn: ['職業', '小さい進歩', '大きい進歩'] }]
break
case undefined:
break
default:
unreachable(searchCondition.cardType)
}
const where: CardWhereInput = {
hasRevisionWith: [{ key: revisionKey }],
hasProductsWith:
searchCondition.productID !== undefined ? [{ id: searchCondition.productID }] : undefined,
hasDeckWith:
searchCondition.deckID !== undefined ? [{ id: searchCondition.deckID }] : undefined,
hasCardTypeWith,
nameJaContains: searchCondition.nameJa,
nameEnContains: searchCondition.nameEn,
descriptionContains: searchCondition.description,
}
return where
}

type CardsExplorerProps = Readonly<{
revisionKey: RevisionKey
decks: Readonly<DeckSummary[]>
Expand All @@ -101,7 +34,7 @@ const CardsExplorer: FC<CardsExplorerProps> = ({ revisionKey, decks, products })
const hasMore = pageInfo === undefined || pageInfo.hasNextPage

const params = new URLSearchParams(window.location.search)
const searchCondition: CardsSearchCondition = paramsToSearchCondition(params)
const searchCondition = paramsToSearchCondition(params)
const where = searchConditionToWhere(revisionKey, searchCondition)

const client = new GraphQLClient('https://api.db.agricolajp.dev/graphql')
Expand Down Expand Up @@ -150,6 +83,11 @@ const CardsExplorer: FC<CardsExplorerProps> = ({ revisionKey, decks, products })
}
}, [hasMore, spinnerRef, fetchMore])

let translationPrintPath = `/${revisionKey}/cards-translation-print/`
if (window.location.search) {
translationPrintPath += `${window.location.search}`
}

return (
<Row>
<Col md={4}>
Expand All @@ -161,7 +99,16 @@ const CardsExplorer: FC<CardsExplorerProps> = ({ revisionKey, decks, products })
onSubmit={onSubmitSearch}
searchCondition={searchCondition}
/>
{totalCount !== undefined && <p className="mt-2">{totalCount}件ヒットしました</p>}
{totalCount !== undefined && (
<>
<p className="mt-2 text-success">
<FaRegCircleCheck /> {totalCount}件ヒットしました
</p>
<Button variant="light" size="sm" href={translationPrintPath}>
<FaPrint /> 翻訳シートとして印刷する
</Button>
</>
)}
</div>
</Col>
<Col md={8}>
Expand Down
4 changes: 2 additions & 2 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ const ogImageAlt = ogImage?.alt ?? 'Agricola DB: Agricola Database for Japanese'
],
}}
/>
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=G-D2KF0XCCVL"
<script type="text/partytown" is:inline src="https://www.googletagmanager.com/gtag/js?id=G-D2KF0XCCVL"
></script>
<script type="text/partytown">
<script type="text/partytown" is:inline>
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
Expand Down
83 changes: 83 additions & 0 deletions src/layouts/PrintLayout.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
import '@fontsource/noto-sans-jp/400.css'
import { SEO } from 'astro-seo'
import './print.scss'
interface Props {
title?: string
description?: string
ogImage?: OGImage
pathname: string
}
type OGImage = Readonly<{
path: string
alt: string
}>
const { title, pathname, ogImage } = Astro.props
const canonicalUrl = new URL(pathname, Astro.site).toString()
const description =
Astro.props.description ??
'ボードゲーム「アグリコラ」に関する情報をまとめたWebサイトです。製品版の全てのカードを掲載する予定です。'
const ogImagePath = ogImage?.path ?? '/ogimage.png'
const ogImageFullPath = new URL(ogImagePath, Astro.site).toString()
const ogImageAlt = ogImage?.alt ?? 'Agricola DB: Agricola Database for Japanese'
---

<!doctype html>
<html lang="ja">
<head>
<SEO
title={title}
titleTemplate="%s - AgricolaDB"
titleDefault="AgricolaDB"
description={description}
canonical={canonicalUrl}
charset="UTF-8"
openGraph={{
basic: {
title: title ?? 'AgricolaDB',
type: 'website',
url: canonicalUrl,
image: ogImageFullPath,
},
optional: {
siteName: 'AgricolaDB',
description,
locale: 'ja_JP',
},
image: {
width: 800,
height: 418,
alt: ogImageAlt,
},
}}
twitter={{
card: 'summary_large_image',
image: ogImageFullPath,
imageAlt: ogImageAlt,
}}
extend={{
link: [{ rel: 'icon', href: '/favicon.ico' }],
meta: [
{ name: 'viewport', content: 'width=device-width' },
{ name: 'generator', content: Astro.generator },
],
}}
/>
<script type="text/partytown" is:inline src="https://www.googletagmanager.com/gtag/js?id=G-D2KF0XCCVL"
></script>
<script type="text/partytown" is:inline>
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
gtag('js', new Date())
gtag('config', 'G-D2KF0XCCVL')
</script>
</head>
<body>
<div class="main"><slot /></div>
</body>
</html>
Loading

0 comments on commit 7d20721

Please sign in to comment.