Files
twitter-lite/src/features/decks/deck-page.tsx
T

186 lines
5.7 KiB
TypeScript

import { useState } from 'react'
import { AppShell } from '#/components/app-shell'
import { ColumnEditor } from './column-editor'
import { ResearchColumn } from './deck-column'
import { type DeckColumn, MAX_COLUMNS, moveColumn } from './model'
import { useDeck } from './use-deck'
import { useDeckTools } from './webmcp-tools'
import './decks.css'
export function DeckPage() {
const { deck, ready, storageError, save } = useDeck()
const [editing, setEditing] = useState<{ id: string } | 'new' | null>(null)
const [renaming, setRenaming] = useState(false)
const [removed, setRemoved] = useState<{
column: DeckColumn
index: number
}>()
useDeckTools({
deck,
ready,
storageError,
save,
onApplied: () => {
setEditing(null)
setRenaming(false)
setRemoved(undefined)
},
})
function saveColumn(column: DeckColumn) {
save({
...deck,
columns:
editing === 'new'
? [...deck.columns, column]
: deck.columns.map((current) =>
current.id === column.id ? column : current,
),
})
setEditing(null)
}
return (
<AppShell active="deck">
<div className="deck-heading">
<div>
<h1>{deck.title}</h1>
<p className="deck-help">
観点ごとに検索を並べて、投稿を読み比べる。
</p>
</div>
<div className="deck-actions">
<button
type="button"
disabled={!ready}
onClick={() => setRenaming(!renaming)}
>
名前を変更
</button>
<button
className="deck-primary"
type="button"
disabled={
!ready || deck.columns.length >= MAX_COLUMNS || editing !== null
}
onClick={() => setEditing('new')}
>
カラムを追加
</button>
</div>
</div>
<p className="deck-save-status">
{!ready
? 'デッキを読み込んでいます…'
: storageError
? 'デッキの保存状態を確認してください。'
: `このブラウザにデッキの条件を保存 · ${deck.columns.length} / ${MAX_COLUMNS} カラム`}
</p>
{storageError ? (
<p className="deck-error" role="alert">
{storageError}
</p>
) : null}
{renaming ? (
<form
className="deck-rename"
onSubmit={(event) => {
event.preventDefault()
const title = String(
new FormData(event.currentTarget).get('title') ?? '',
).trim()
if (!title) return
save({ ...deck, title })
setRenaming(false)
}}
>
<label htmlFor="deck-title">
調査テーマ・デッキ名
<input
id="deck-title"
name="title"
defaultValue={deck.title}
maxLength={120}
required
/>
</label>
<button type="submit">名前を保存</button>
</form>
) : null}
{editing !== null ? (
<ColumnEditor
key={editing === 'new' ? 'new' : editing.id}
column={
editing === 'new'
? undefined
: deck.columns.find((column) => column.id === editing.id)
}
onSave={saveColumn}
onCancel={() => setEditing(null)}
/>
) : null}
{removed ? (
<div className="deck-undo" role="status">
<span>「{removed.column.title}」を削除しました。</span>
<button
type="button"
disabled={deck.columns.length >= MAX_COLUMNS || editing === 'new'}
onClick={() => {
const columns = [...deck.columns]
columns.splice(removed.index, 0, removed.column)
save({ ...deck, columns })
setRemoved(undefined)
}}
>
元に戻す
</button>
</div>
) : null}
{ready && deck.columns.length === 0 && editing === null ? (
<div className="deck-empty">
<h2>最初の観点を追加しましょう。</h2>
<p>
「日本語での反応」「発表元の投稿」など、調べたい切り口に名前と検索条件を付けてください。
</p>
<button
className="deck-primary"
type="button"
onClick={() => setEditing('new')}
>
最初のカラムを作る
</button>
</div>
) : null}
{ready && deck.columns.length > 0 ? (
// biome-ignore lint/a11y/noNoninteractiveTabindex: Keyboard users must be able to scroll the column board.
<section className="deck-board" aria-label="調査カラム" tabIndex={0}>
{deck.columns.map((column, index) => (
<ResearchColumn
key={column.id}
column={column}
first={index === 0}
last={index === deck.columns.length - 1}
onEdit={() => setEditing({ id: column.id })}
onMove={(direction) =>
save(moveColumn(deck, column.id, direction))
}
onRemove={() => {
setRemoved({ column, index })
save({
...deck,
columns: deck.columns.filter(
(current) => current.id !== column.id,
),
})
if (typeof editing === 'object' && editing?.id === column.id)
setEditing(null)
}}
/>
))}
</section>
) : null}
</AppShell>
)
}