設定画面 (#34) #397

マージ済み
みてるぞ が 31 個のコミットを feature/034 から main へマージ 2026-07-07 00:48:41 +09:00
3個のファイルの変更94行の追加72行の削除
コミット 4478bc92ed の変更だけを表示してゐます - すべてのコミットを表示
+6 -1
ファイルの表示
@@ -12,7 +12,8 @@ import TopNav from '@/components/TopNav'
import DialogueProvider from '@/components/dialogues/DialogueProvider' import DialogueProvider from '@/components/dialogues/DialogueProvider'
import { Toaster } from '@/components/ui/toaster' import { Toaster } from '@/components/ui/toaster'
import { apiPost, isApiError } from '@/lib/api' import { apiPost, isApiError } from '@/lib/api'
import { applyClientAppearance, import { applyClientAnimationMode,
applyClientAppearance,
fetchUserThemeSlots, fetchUserThemeSlots,
fetchUserSettings, fetchUserSettings,
getClientThemeMode, getClientThemeMode,
@@ -142,6 +143,10 @@ const App: FC = () => {
return () => mediaQuery.removeEventListener ('change', handleThemeChange) return () => mediaQuery.removeEventListener ('change', handleThemeChange)
}, []) }, [])
useEffect (() => {
applyClientAnimationMode (animationMode)
}, [animationMode])
useEffect (() => { useEffect (() => {
const createUser = async () => { const createUser = async () => {
const data = await apiPost<{ code: string; user: User }> ('/users') const data = await apiPost<{ code: string; user: User }> ('/users')
+78 -65
ファイルの表示
@@ -30,9 +30,6 @@ import type { FC, MutableRefObject, ReactNode } from 'react'
import type { Category, Post, TagWithSections } from '@/types' import type { Category, Post, TagWithSections } from '@/types'
type TagByCategory = { [key in Category]: TagWithSections[] } type TagByCategory = { [key in Category]: TagWithSections[] }
type FlatTagRow = {
tag: TagWithSections
parentTagId?: number }
const renderTagTree = ( const renderTagTree = (
@@ -64,34 +61,6 @@ const renderTagTree = (
?? [])] ?? [])]
} }
const flattenTagTree = (
tags: TagWithSections[],
): FlatTagRow[] => {
const seen = new Set<number> ()
const rows: FlatTagRow[] = []
const visit = (
tag: TagWithSections,
parentTagId?: number,
) => {
if (seen.has (tag.id))
return
seen.add (tag.id)
rows.push ({ tag, parentTagId })
for (const child of tag.children ?? [])
visit (child, tag.id)
}
for (const tag of tags)
visit (tag)
return rows.sort ((rowA, rowB) => rowA.tag.name < rowB.tag.name ? -1 : 1)
}
const isDescendant = ( const isDescendant = (
root: TagWithSections, root: TagWithSections,
targetId: number, targetId: number,
@@ -158,6 +127,37 @@ const buildTagByCategory = (post: Post): TagByCategory => {
} }
const buildFlatTagByCategory = (
byCategory: TagByCategory,
): TagByCategory => {
const tagsTmp = { } as TagByCategory
const seen = new Set<number> ()
for (const category of CATEGORIES)
tagsTmp[category] = []
const visit = (tag: TagWithSections) => {
if (seen.has (tag.id))
return
seen.add (tag.id)
tagsTmp[tag.category].push ({ ...tag, children: [] })
for (const child of tag.children ?? [])
visit (child)
}
for (const category of CATEGORIES)
for (const tag of byCategory[category] ?? [])
visit (tag)
for (const category of CATEGORIES)
tagsTmp[category].sort ((tagA, tagB) => tagA.name < tagB.name ? -1 : 1)
return tagsTmp
}
const changeCategory = async ( const changeCategory = async (
tagId: number, tagId: number,
category: Category): Promise<void> => { category: Category): Promise<void> => {
@@ -210,6 +210,10 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
const [dragging, setDragging] = useState (false) const [dragging, setDragging] = useState (false)
const [saving, setSaving] = useState (false) const [saving, setSaving] = useState (false)
const [tags, setTags] = useState (baseTags) const [tags, setTags] = useState (baseTags)
const flatTagsByCategory = useMemo<TagByCategory> (
() => buildFlatTagByCategory (tags),
[tags],
)
const suppressClickRef = useRef (false) const suppressClickRef = useRef (false)
@@ -343,41 +347,50 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
document.body.style.userSelect = '' document.body.style.userSelect = ''
}} }}
modifiers={[restrictToWindowEdges]}> modifiers={[restrictToWindowEdges]}>
{CATEGORIES.map ((cat: Category) => ((tags[cat] ?? []).length > 0 || dragging) && ( {CATEGORIES.map ((cat: Category) => {
<div className="my-3" key={cat}> const categoryTags =
<SubsectionTitle> tagRelationDisplay === 'grouped'
<motion.div ? (tags[cat] ?? [])
layoutId={`tag-${ sp ? 'sp-' : '' }${ cat }`} : (flatTagsByCategory[cat] ?? [])
transition={{ layout: { duration: .2, ease: 'easeOut' } }}>
{CATEGORY_NAMES[cat]}
</motion.div>
</SubsectionTitle>
<ul> if (!(categoryTags.length > 0 || dragging))
{(tagRelationDisplay === 'grouped' return null
? (tags[cat] ?? []).flatMap (tag =>
renderTagTree ( return (
tag, <div className="my-3" key={cat}>
0, <SubsectionTitle>
`cat-${ cat }`, <motion.div
suppressClickRef, layoutId={`tag-${ sp ? 'sp-' : '' }${ cat }`}
undefined, transition={{ layout: { duration: .2, ease: 'easeOut' } }}>
sp, {CATEGORY_NAMES[cat]}
), </motion.div>
) </SubsectionTitle>
: flattenTagTree (tags[cat] ?? []).map (row => (
<li key={`flat-${ cat }-${ row.tag.id }`} className="mb-1"> <ul>
<DraggableDroppableTagRow {(tagRelationDisplay === 'grouped'
tag={row.tag} ? (tags[cat] ?? []).flatMap (tag =>
nestLevel={0} renderTagTree (
pathKey={`flat-${ cat }-${ row.tag.id }`} tag,
parentTagId={row.parentTagId} 0,
suppressClickRef={suppressClickRef} `cat-${ cat }`,
sp={sp}/> suppressClickRef,
</li>)))} undefined,
<DropSlot cat={cat}/> sp,
</ul> ),
</div>))} )
: (flatTagsByCategory[cat] ?? []).map (tag => (
<li key={`flat-${ cat }-${ tag.id }`} className="mb-1">
<DraggableDroppableTagRow
tag={tag}
nestLevel={0}
pathKey={`flat-${ cat }-${ tag.id }`}
suppressClickRef={suppressClickRef}
sp={sp}/>
</li>)))}
<DropSlot cat={cat}/>
</ul>
</div>)
})}
{post && ( {post && (
<motion.div <motion.div
layoutId={`post-info-${ sp }`} layoutId={`post-info-${ sp }`}
+10 -6
ファイルの表示
@@ -161,13 +161,17 @@ const TopNav: FC<Props> = ({ user }) => {
visibleMenu.findIndex (item => location.pathname.startsWith (item.base || item.to)) visibleMenu.findIndex (item => location.pathname.startsWith (item.base || item.to))
const submenuHeight = moreVsbl ? 40 * moreMenu.length : (activeIdx < 0 ? 0 : 40) const submenuHeight = moreVsbl ? 40 * moreMenu.length : (activeIdx < 0 ? 0 : 40)
const topNavTransition = const topNavTransition =
reducedAnimations animationsOff
? { duration: .08, ease: 'linear' as const } ? { duration: 0 }
: { duration: .2, ease: 'easeOut' as const } : reducedAnimations
? { duration: .08, ease: 'linear' as const }
: { duration: .2, ease: 'easeOut' as const }
const opacityTransition = const opacityTransition =
reducedAnimations animationsOff
? { duration: .08 } ? { duration: 0 }
: { duration: .12 } : reducedAnimations
? { duration: .08 }
: { duration: .12 }
const highlightTransitionClass = const highlightTransitionClass =
animationsOff animationsOff
? undefined ? undefined