このコミットが含まれているのは:
+37
-37
@@ -155,7 +155,7 @@ const PostDetailRoute = ({ user }: { user: User | null }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const App: FC = () => {
|
const RoutedApp: FC = () => {
|
||||||
const [user, setUser] = useState<User | null> (null)
|
const [user, setUser] = useState<User | null> (null)
|
||||||
const [status, setStatus] = useState (200)
|
const [status, setStatus] = useState (200)
|
||||||
const behaviourSettings = useClientBehaviourSettings ()
|
const behaviourSettings = useClientBehaviourSettings ()
|
||||||
@@ -172,41 +172,6 @@ const App: FC = () => {
|
|||||||
),
|
),
|
||||||
[animationMode],
|
[animationMode],
|
||||||
)
|
)
|
||||||
const router = useMemo (
|
|
||||||
() => createBrowserRouter ([{
|
|
||||||
path: '*',
|
|
||||||
element: (
|
|
||||||
<DialogueProvider>
|
|
||||||
<UnsavedChangesGuardProvider>
|
|
||||||
<KeyboardShortcutsProvider>
|
|
||||||
<MotionConfig
|
|
||||||
reducedMotion={
|
|
||||||
animationMode === 'normal'
|
|
||||||
? 'never'
|
|
||||||
: animationMode === 'reduced'
|
|
||||||
? 'user'
|
|
||||||
: 'always'
|
|
||||||
}>
|
|
||||||
<LayoutWrapper>
|
|
||||||
<motion.div
|
|
||||||
layout={animationMode === 'off' ? false : 'position'}
|
|
||||||
transition={{ layout: appLayoutTransition }}
|
|
||||||
className="relative flex h-dvh w-full flex-col overflow-y-hidden">
|
|
||||||
<TopNav user={user}/>
|
|
||||||
<RouteTransitionWrapper
|
|
||||||
animationMode={animationMode}
|
|
||||||
user={user}
|
|
||||||
setUser={setUser}/>
|
|
||||||
</motion.div>
|
|
||||||
</LayoutWrapper>
|
|
||||||
</MotionConfig>
|
|
||||||
|
|
||||||
<Toaster/>
|
|
||||||
</KeyboardShortcutsProvider>
|
|
||||||
</UnsavedChangesGuardProvider>
|
|
||||||
</DialogueProvider>) }]),
|
|
||||||
[LayoutWrapper, animationMode, appLayoutTransition, setUser, user],
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
applyClientAppearance ()
|
applyClientAppearance ()
|
||||||
@@ -289,11 +254,46 @@ const App: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<DialogueProvider>
|
||||||
|
<UnsavedChangesGuardProvider>
|
||||||
|
<KeyboardShortcutsProvider>
|
||||||
|
<MotionConfig
|
||||||
|
reducedMotion={
|
||||||
|
animationMode === 'normal'
|
||||||
|
? 'never'
|
||||||
|
: animationMode === 'reduced'
|
||||||
|
? 'user'
|
||||||
|
: 'always'
|
||||||
|
}>
|
||||||
|
<LayoutWrapper>
|
||||||
|
<motion.div
|
||||||
|
layout={animationMode === 'off' ? false : 'position'}
|
||||||
|
transition={{ layout: appLayoutTransition }}
|
||||||
|
className="relative flex h-dvh w-full flex-col overflow-y-hidden">
|
||||||
|
<TopNav user={user}/>
|
||||||
|
<RouteTransitionWrapper
|
||||||
|
animationMode={animationMode}
|
||||||
|
user={user}
|
||||||
|
setUser={setUser}/>
|
||||||
|
</motion.div>
|
||||||
|
</LayoutWrapper>
|
||||||
|
</MotionConfig>
|
||||||
|
|
||||||
|
<Toaster/>
|
||||||
|
</KeyboardShortcutsProvider>
|
||||||
|
</UnsavedChangesGuardProvider>
|
||||||
|
</DialogueProvider>)
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = createBrowserRouter ([{
|
||||||
|
path: '*',
|
||||||
|
element: <RoutedApp/> }])
|
||||||
|
|
||||||
|
const App: FC = () => (
|
||||||
<>
|
<>
|
||||||
<RouteBlockerOverlay/>
|
<RouteBlockerOverlay/>
|
||||||
{import.meta.env.DEV && <DevModeWatermark/>}
|
{import.meta.env.DEV && <DevModeWatermark/>}
|
||||||
<RouterProvider router={router}/>
|
<RouterProvider router={router}/>
|
||||||
</>)
|
</>)
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useBlocker, useLocation } from 'react-router-dom'
|
import { useBlocker } from 'react-router-dom'
|
||||||
|
|
||||||
import { useDialogue } from '@/lib/dialogues/useDialogue'
|
import { useDialogue } from '@/lib/dialogues/useDialogue'
|
||||||
|
|
||||||
@@ -34,7 +34,6 @@ const UnsavedChangesGuardContext =
|
|||||||
|
|
||||||
export const UnsavedChangesGuardProvider: FC<PropsWithChildren> = ({ children }) => {
|
export const UnsavedChangesGuardProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
const dialogue = useDialogue ()
|
const dialogue = useDialogue ()
|
||||||
const location = useLocation ()
|
|
||||||
const sourcesRef = useRef (new Map<symbol, UnsavedChangesSource> ())
|
const sourcesRef = useRef (new Map<symbol, UnsavedChangesSource> ())
|
||||||
const bypassNextNavigationRef = useRef (false)
|
const bypassNextNavigationRef = useRef (false)
|
||||||
const [revision, setRevision] = useState (0)
|
const [revision, setRevision] = useState (0)
|
||||||
@@ -68,12 +67,19 @@ export const UnsavedChangesGuardProvider: FC<PropsWithChildren> = ({ children })
|
|||||||
[sources],
|
[sources],
|
||||||
)
|
)
|
||||||
const hasUnsavedChanges = dirtySources.length > 0
|
const hasUnsavedChanges = dirtySources.length > 0
|
||||||
const blocker = useBlocker (
|
const shouldBlock = useCallback (() => {
|
||||||
useCallback (
|
if (!(hasUnsavedChanges))
|
||||||
() => hasUnsavedChanges && !(bypassNextNavigationRef.current),
|
return false
|
||||||
[hasUnsavedChanges],
|
|
||||||
),
|
if (bypassNextNavigationRef.current)
|
||||||
)
|
{
|
||||||
|
bypassNextNavigationRef.current = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}, [hasUnsavedChanges])
|
||||||
|
const blocker = useBlocker (shouldBlock)
|
||||||
|
|
||||||
const discardDirtyChanges = useCallback (async (): Promise<boolean> => {
|
const discardDirtyChanges = useCallback (async (): Promise<boolean> => {
|
||||||
if (!(hasUnsavedChanges))
|
if (!(hasUnsavedChanges))
|
||||||
@@ -93,8 +99,6 @@ export const UnsavedChangesGuardProvider: FC<PropsWithChildren> = ({ children })
|
|||||||
if (!(confirmed))
|
if (!(confirmed))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
bypassNextNavigationRef.current = true
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (const source of dirtySources)
|
for (const source of dirtySources)
|
||||||
@@ -102,7 +106,6 @@ export const UnsavedChangesGuardProvider: FC<PropsWithChildren> = ({ children })
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
bypassNextNavigationRef.current = false
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,10 +142,6 @@ export const UnsavedChangesGuardProvider: FC<PropsWithChildren> = ({ children })
|
|||||||
}
|
}
|
||||||
}, [blocker, discardDirtyChanges])
|
}, [blocker, discardDirtyChanges])
|
||||||
|
|
||||||
useEffect (() => {
|
|
||||||
bypassNextNavigationRef.current = false
|
|
||||||
}, [location.hash, location.pathname, location.search])
|
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
if (!(hasUnsavedChanges))
|
if (!(hasUnsavedChanges))
|
||||||
return
|
return
|
||||||
@@ -183,14 +182,16 @@ export const useUnsavedChangesGuard = (
|
|||||||
if (context == null)
|
if (context == null)
|
||||||
throw new Error ('UnsavedChangesGuardProvider が必要です.')
|
throw new Error ('UnsavedChangesGuardProvider が必要です.')
|
||||||
|
|
||||||
|
const { registerUnsavedChangesSource } = context
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
if (options == null)
|
if (options == null)
|
||||||
return
|
return
|
||||||
|
|
||||||
return context.registerUnsavedChangesSource ({
|
return registerUnsavedChangesSource ({
|
||||||
dirty: options.dirty,
|
dirty: options.dirty,
|
||||||
discard: options.onDiscard ?? (() => undefined) })
|
discard: options.onDiscard ?? (() => undefined) })
|
||||||
}, [context, options?.dirty, options?.onDiscard])
|
}, [options?.dirty, options?.onDiscard, registerUnsavedChangesSource])
|
||||||
|
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする