Skip to content

Commit

Permalink
Add: ボイボ寮キャラクターの呼称のコピー機能 + リファクタリング (#168)
Browse files Browse the repository at this point in the history
Co-authored-by: Hiroshiba <[email protected]>
  • Loading branch information
wappon28dev and Hiroshiba authored Oct 4, 2023
1 parent 2f9674b commit f3025fe
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 76 deletions.
25 changes: 24 additions & 1 deletion src/components/layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,7 @@ $call-names-cell-width-column: 190px;
position: sticky;
top: 0;
left: 0;
z-index: 1;
}

th,
Expand Down Expand Up @@ -932,10 +933,32 @@ $call-names-cell-width-column: 190px;
flex-direction: column;
justify-content: center;

position: relative;

// コピーボタン
.icon {
@extend .m-1;

visibility: hidden;
position: absolute;
height: 1rem;
width: 1rem;
top: 0;
right: 0;

&.selected {
visibility: visible;
}
}

&:hover {
// NOTE: ちらつくので border ではなく outline
outline: 2px solid;
border-radius: $radius;

.icon {
visibility: visible;
}
}
}
}
Expand All @@ -950,7 +973,7 @@ $call-names-cell-width-column: 190px;
// 左上のセル
&.origin {
width: $call-names-cell-width-column;
z-index: 1;
z-index: 2;
border-right: $call-names-border;
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/dormitory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ const Dormitory: React.FC<DormitoryProps> = ({ setShowingHeader }) => {
<div className="has-text-centered pt-2 pb-0">
<h2 className="title is-4">関連コンテンツ</h2>
<Link
to="/dormitory/call-names/"
to="/dormitory/call_names/"
className="button is-normal is-rounded"
type="button"
role={"button"}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { Link } from "gatsby"
import { getSrc, getSrcSet } from "gatsby-plugin-image"
import React, { CSSProperties, ReactElement, useContext } from "react"
import { Page } from "../../../components/page"
import Seo from "../../../components/seo"
import { CharacterContext } from "../../../contexts/context"
import { useDetailedCharacterInfo } from "../../../hooks/useDetailedCharacterInfo"
import { CharacterKey } from "../../../types/dormitoryCharacter"

import { DormitoryExplainComponent } from "../../dormitory"
import React, {
CSSProperties,
ReactElement,
useContext,
MouseEvent,
useState,
useEffect,
} from "react"
import { Page } from "../../components/page"
import Seo from "../../components/seo"
import { CharacterContext } from "../../contexts/context"
import { useDetailedCharacterInfo } from "../../hooks/useDetailedCharacterInfo"
import { CharacterKey } from "../../types/dormitoryCharacter"
import { DormitoryExplainComponent } from "../dormitory"
import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"

function hex2rgba(hex: string, alpha = 1): [number, number, number, number] {
const match = hex.match(/\w\w/g)
Expand All @@ -28,74 +36,139 @@ function rgba2rgbOnWhite(
return [_red, _green, _blue]
}

export default function CallNamesPage() {
// FIXME: Row
function Column({
characterKey,
}: {
characterKey: CharacterKey
}): ReactElement {
const { characterInfos, callNameInfos } = useDetailedCharacterInfo()
const { characterKeys } = useContext(CharacterContext)

function getCharacterImage(characterKey: CharacterKey): ReactElement {
const characterInfo = characterInfos[characterKey]
return (
<img
src={getSrc(characterInfo.bustupImage)}
srcSet={getSrcSet(characterInfo.bustupImage)}
alt={characterInfo.name}
/>
)
const [selectedCallName, setSelectedCallName] = useState<string>()
const [showCopiedIcon, setShowCopiedIcon] = useState(false)

const callNameInfo = callNameInfos[characterKey]
const characterInfo = characterInfos[characterKey]
const outlineStyle: CSSProperties = {
outlineColor: characterInfo.color,
}

function getColumn(characterKey: CharacterKey): ReactElement {
const callNameInfo = callNameInfos[characterKey]
const characterInfo = characterInfos[characterKey]
useEffect(() => {
if (selectedCallName == undefined) return

const timer = setTimeout(() => {
setShowCopiedIcon(false)
}, 1500)

const outlineStyle: CSSProperties = {
outlineColor: characterInfo.color,
return () => {
clearTimeout(timer)
}
}, [showCopiedIcon, selectedCallName])

function copyToClipboard(event: MouseEvent<HTMLInputElement>): void {
const callName = event.currentTarget.innerText

setSelectedCallName(callName)
navigator.clipboard.writeText(callName)
setShowCopiedIcon(true)
}

function Cell({
callName,
externalClassName,
}: {
callName: string
externalClassName?: string
}): ReactElement {
const isSelected = selectedCallName === callName && showCopiedIcon

return (
<>
{characterKeys.map(_characterKey => {
const callName = callNameInfo[_characterKey]

return (
<td key={_characterKey}>
<div>
{(() => {
if (characterKey === _characterKey) {
return callNameInfo.me.map(part => (
<p style={outlineStyle} key={part} className="me">
{part}
</p>
))
}

if (callName == undefined) {
return (
<p style={outlineStyle} className="unknown">
?
</p>
)
}

return callName.split("/").map(part => (
<p style={outlineStyle} key={part}>
{part}
<p
className={externalClassName}
onClick={copyToClipboard}
style={outlineStyle}
title={`クリックして呼称をコピー:「${callName}」`}
>
<span className={`icon ${isSelected ? "selected" : ""}`}>
<FontAwesomeIcon
icon={isSelected ? faCheck : faCopy}
color={characterInfo.color}
/>
</span>
{callName}
</p>
)
}

return (
<>
{characterKeys.map(_characterKey => {
const callName = callNameInfo[_characterKey]

return (
<td key={_characterKey}>
<div>
{(() => {
if (_characterKey === characterKey) {
return callNameInfo.me.map(part => (
<Cell
key={`me-${part}`}
callName={part}
externalClassName="me"
/>
))
}

if (callName == undefined) {
return (
<p
key={`${_characterKey}-unknown`}
className="unknown"
style={outlineStyle}
>
</p>
)
}

return callName
.split("/")
.map(part => (
<Cell key={`${_characterKey}-${part}`} callName={part} />
))
})()}
</div>
</td>
)
})}
<td className="you">
<div>
{callNameInfo.you.map(part => (
<p style={outlineStyle} key={part}>
{part}
</p>
))}
</div>
</td>
</>
})()}
</div>
</td>
)
})}
<td className="you">
<div>
{callNameInfo.you.map(part => (
<Cell key={`you-${part}`} callName={part} />
))}
</div>
</td>
</>
)
}

export default function CallNamesPage() {
const { characterInfos } = useDetailedCharacterInfo()
const { characterKeys } = useContext(CharacterContext)

function CharacterImage({
characterKey,
}: {
characterKey: CharacterKey
}): ReactElement {
const characterInfo = characterInfos[characterKey]
return (
<img
src={getSrc(characterInfo.bustupImage)}
srcSet={getSrcSet(characterInfo.bustupImage)}
alt={characterInfo.name}
/>
)
}

Expand Down Expand Up @@ -138,7 +211,7 @@ export default function CallNamesPage() {
return (
<th key={characterKey}>
<Link to={`/dormitory/${characterInfo.id}/`}>
{getCharacterImage(characterKey)}
<CharacterImage characterKey={characterKey} />
<p
style={{
color: characterInfo.color,
Expand Down Expand Up @@ -168,19 +241,14 @@ export default function CallNamesPage() {
const backgroundColor = `rgb(${red}, ${green}, ${blue})`

return (
// FIXME: #id でキャラクターに直接アクセスするとスクロールがずれるのを直す
<tr
key={characterKey}
id={characterInfo.id}
style={{ backgroundColor }}
>
<tr key={characterKey} style={{ backgroundColor }}>
<th
style={{
backgroundColor,
}}
>
<Link to={`/dormitory/${characterInfo.id}/`}>
{getCharacterImage(characterKey)}
<CharacterImage characterKey={characterKey} />
<p
style={{
color: characterInfo.color,
Expand All @@ -190,7 +258,7 @@ export default function CallNamesPage() {
</p>
</Link>
</th>
{getColumn(characterKey)}
<Column characterKey={characterKey} />
</tr>
)
})}
Expand Down

0 comments on commit f3025fe

Please sign in to comment.