-
+ options={themeModeSelections.map (selection => ({
+ value: selection.id,
+ label: selection.label }))}
+ onChange={value => onSelectThemeMode (value as ClientThemeMode)}/>
-
-
-
-
-
-
-
+
+
-
変更は保存時に反映されます。
+
保存するまで反映は確定しません。
@@ -672,6 +704,7 @@ const SettingPage: FC
= ({ user, setUser }) => {
const location = useLocation ()
const navigate = useNavigate ()
const dialogue = useDialogue ()
+ const { registerUnsavedChangesSource } = useUnsavedChangesGuard ()
const [inheritVsbl, setInheritVsbl] = useState (false)
const [loading, setLoading] = useState (true)
@@ -693,10 +726,6 @@ const SettingPage: FC = ({ user, setUser }) => {
useState (() => ensureCompleteThemeSlots ({ }))
const [draftThemeSlots, setDraftThemeSlots] =
useState (() => ensureCompleteThemeSlots ({ }))
- const [savedEditingThemeSlot, setSavedEditingThemeSlot] =
- useState ('user:light:1')
- const [editingThemeSlot, setEditingThemeSlot] =
- useState ('user:light:1')
const [dirtyStates, setDirtyStates] =
useState>> ({ })
const [userCodeVsbl, setUserCodeVsbl] = useState (false)
@@ -724,7 +753,9 @@ const SettingPage: FC = ({ user, setUser }) => {
const selectedTheme = useMemo (
() => {
const currentSelection =
- themeSelections.find (selection => selection.id === editingThemeSlot)
+ themeSelections.find (
+ selection => selection.id === displayedThemeSelection.selection,
+ )
?? themeSelections[0]
const themeSlot =
draftThemeSlots[currentSelection.id]
@@ -736,25 +767,23 @@ const SettingPage: FC = ({ user, setUser }) => {
baseTheme: currentSelection.baseTheme,
tokens: themeSlot.tokens }
},
- [draftThemeSlots, editingThemeSlot])
+ [displayedThemeSelection.selection, draftThemeSlots])
const hasUnsavedThemeChanges = useMemo (
() =>
serialiseThemeDraft (draftThemeMode,
draftActiveLightThemeSlotNo,
draftActiveDarkThemeSlotNo,
draftThemeSlots)
- !== (serialiseThemeDraft (savedThemeMode,
- savedActiveLightThemeSlotNo,
- savedActiveDarkThemeSlotNo,
- savedThemeSlots)),
+ !== serialiseThemeDraft (savedThemeMode,
+ savedActiveLightThemeSlotNo,
+ savedActiveDarkThemeSlotNo,
+ savedThemeSlots),
[draftActiveDarkThemeSlotNo,
draftActiveLightThemeSlotNo,
draftThemeMode,
draftThemeSlots,
- editingThemeSlot,
savedActiveDarkThemeSlotNo,
savedActiveLightThemeSlotNo,
- savedEditingThemeSlot,
savedThemeMode,
savedThemeSlots])
const savedName = user?.name ?? ''
@@ -812,18 +841,15 @@ const SettingPage: FC = ({ user, setUser }) => {
activeLightThemeSlotNo = draftActiveLightThemeSlotNo,
activeDarkThemeSlotNo = draftActiveDarkThemeSlotNo,
themeSlots = draftThemeSlots,
- nextEditingThemeSlot = editingThemeSlot,
}: { themeMode?: ClientThemeMode
activeLightThemeSlotNo?: UserThemeSlotNo
activeDarkThemeSlotNo?: UserThemeSlotNo
- themeSlots?: UserThemeSlotMap
- nextEditingThemeSlot?: UserThemeSlotSelection },
+ themeSlots?: UserThemeSlotMap },
) => {
setDraftThemeMode (themeMode)
setDraftActiveLightThemeSlotNo (activeLightThemeSlotNo)
setDraftActiveDarkThemeSlotNo (activeDarkThemeSlotNo)
setDraftThemeSlots (themeSlots)
- setEditingThemeSlot (nextEditingThemeSlot)
setCachedUserThemeSlots (themeSlots)
applyThemeAppearanceState ({
themeMode,
@@ -836,26 +862,23 @@ const SettingPage: FC = ({ user, setUser }) => {
clearUserValidationErrors ()
}
- const currentThemeDraftState = (): ThemeDraftState => ({
+const currentThemeDraftState = (): ThemeDraftState => ({
themeMode: draftThemeMode,
activeLightThemeSlotNo: draftActiveLightThemeSlotNo,
activeDarkThemeSlotNo: draftActiveDarkThemeSlotNo,
- themeSlots: draftThemeSlots,
- editingThemeSlot })
+ themeSlots: draftThemeSlots })
const savedThemeDraftState = (): ThemeDraftState => ({
themeMode: savedThemeMode,
activeLightThemeSlotNo: savedActiveLightThemeSlotNo,
activeDarkThemeSlotNo: savedActiveDarkThemeSlotNo,
- themeSlots: savedThemeSlots,
- editingThemeSlot: savedEditingThemeSlot })
+ themeSlots: savedThemeSlots })
const discardThemeChanges = () => {
setDraftThemeMode (savedThemeMode)
setDraftActiveLightThemeSlotNo (savedActiveLightThemeSlotNo)
setDraftActiveDarkThemeSlotNo (savedActiveDarkThemeSlotNo)
setDraftThemeSlots (savedThemeSlots)
- setEditingThemeSlot (savedEditingThemeSlot)
setCachedUserThemeSlots (savedThemeSlots)
setClientThemeAppearance ({
themeMode: savedThemeMode,
@@ -864,6 +887,18 @@ const SettingPage: FC = ({ user, setUser }) => {
applyClientAppearance (savedThemeSlots)
}
+ const discardAllDirtyChanges = async (): Promise => {
+ const currentDirtyStates = Object.values (dirtyStates)
+
+ for (const dirtyState of currentDirtyStates)
+ {
+ if (dirtyState?.dirty)
+ await dirtyState.discard ()
+ }
+
+ setDirtyStates ({ })
+ }
+
const discardCurrentTabChanges = async (): Promise => {
const currentDirtyState = dirtyStates[activeTab]
@@ -903,10 +938,7 @@ const SettingPage: FC = ({ user, setUser }) => {
cancelText = 'このまま編集する',
confirmText = '変更を破棄して切り替える',
): Promise => {
- const baseState =
- hasUnsavedThemeChanges
- ? savedThemeDraftState ()
- : currentThemeDraftState ()
+ const baseState = currentThemeDraftState ()
if (hasUnsavedThemeChanges)
{
@@ -916,52 +948,52 @@ const SettingPage: FC = ({ user, setUser }) => {
confirmText)
if (!(confirmed))
return
+
+ discardThemeChanges ()
}
- const nextState = buildNextState (baseState)
+ const nextState = buildNextState (
+ hasUnsavedThemeChanges
+ ? savedThemeDraftState ()
+ : baseState)
applyDraftThemeState ({
themeMode: nextState.themeMode,
activeLightThemeSlotNo: nextState.activeLightThemeSlotNo,
activeDarkThemeSlotNo: nextState.activeDarkThemeSlotNo,
- themeSlots: nextState.themeSlots,
- nextEditingThemeSlot: nextState.editingThemeSlot })
+ themeSlots: nextState.themeSlots })
}
- const routeBlocker = useBlocker (({ currentLocation, nextLocation }) =>
- hasPageUnsavedChanges
- && currentLocation.pathname === SETTINGS_PATH
- && nextLocation.pathname !== currentLocation.pathname)
-
- useEffect (() => {
- if (routeBlocker.state !== 'blocked')
+ const requestThemeMode = async (
+ themeMode: ClientThemeMode,
+ ): Promise => {
+ if (themeMode === draftThemeMode)
return
- let active = true
+ await requestThemeStateChange (baseState => ({ ...baseState, themeMode }))
+ }
- void (async () => {
- const confirmed = await confirmDiscardChanges (
- 'このまま移動すると、保存していない変更は失われます。',
- 'このページに残る',
- '変更を破棄して移動',
- )
- if (!(active))
- return
+ const requestLightThemeSlotNo = async (
+ slotNo: UserThemeSlotNo,
+ ): Promise => {
+ if (slotNo === draftActiveLightThemeSlotNo)
+ return
- if (!(confirmed))
- {
- routeBlocker.reset ()
- return
- }
+ await requestThemeStateChange (baseState => ({
+ ...baseState,
+ activeLightThemeSlotNo: slotNo }))
+ }
- await discardCurrentTabChanges ()
- routeBlocker.proceed ()
- }) ()
+ const requestDarkThemeSlotNo = async (
+ slotNo: UserThemeSlotNo,
+ ): Promise => {
+ if (slotNo === draftActiveDarkThemeSlotNo)
+ return
- return () => {
- active = false
- }
- }, [routeBlocker])
+ await requestThemeStateChange (baseState => ({
+ ...baseState,
+ activeDarkThemeSlotNo: slotNo }))
+ }
useBeforeUnload (event => {
if (!(hasPageUnsavedChanges))
@@ -972,9 +1004,9 @@ const SettingPage: FC = ({ user, setUser }) => {
}, { capture: true })
const updateDraftThemeSlot = (
- selection: UserThemeSlotSelection,
updater: (tokens: ThemeTokens) => ThemeTokens,
) => {
+ const selection = displayedThemeSelection.selection
const currentThemeSlot =
draftThemeSlots[selection]
?? ensureCompleteThemeSlots ({ })[selection]
@@ -982,7 +1014,7 @@ const SettingPage: FC = ({ user, setUser }) => {
...draftThemeSlots,
[selection]: { ...currentThemeSlot, tokens: updater (currentThemeSlot.tokens) } }
- applyDraftThemeState ({ themeSlots: nextThemeSlots, nextEditingThemeSlot: selection })
+ applyDraftThemeState ({ themeSlots: nextThemeSlots })
}
const handleTabKeyDown = (event: KeyboardEvent) => {
@@ -1044,21 +1076,13 @@ const SettingPage: FC = ({ user, setUser }) => {
try
{
+ const selection = displayedThemeSelection.selection
let nextSavedThemeSlots = { ...savedThemeSlots }
+ const draftThemeSlot = draftThemeSlots[selection]
+ const savedThemeSlot = savedThemeSlots[selection]
- for (const selection of USER_THEME_SLOT_SELECTIONS)
+ if (JSON.stringify (draftThemeSlot?.tokens) !== JSON.stringify (savedThemeSlot?.tokens))
{
- const draftThemeSlot = draftThemeSlots[selection]
- const savedThemeSlot = savedThemeSlots[selection]
- const requiresPersist =
- selection === editingThemeSlot
- && savedThemeSlot?.createdAt == null
-
- if ((JSON.stringify (draftThemeSlot?.tokens)
- === JSON.stringify (savedThemeSlot?.tokens))
- && !(requiresPersist))
- continue
-
const persisted = await upsertUserThemeSlot (
draftThemeSlot?.baseTheme ?? selection.split (':')[1] as BuiltinThemeId,
draftThemeSlot?.slotNo ?? Number (selection.split (':')[2]) as UserThemeSlotNo,
@@ -1075,8 +1099,6 @@ const SettingPage: FC = ({ user, setUser }) => {
setSavedActiveDarkThemeSlotNo (draftActiveDarkThemeSlotNo)
setSavedThemeSlots (completeThemeSlots)
setDraftThemeSlots (completeThemeSlots)
- setSavedEditingThemeSlot (editingThemeSlot)
- setEditingThemeSlot (editingThemeSlot)
// Display mode and active slot numbers are browser-local state.
setClientThemeAppearance ({
themeMode: draftThemeMode,
@@ -1128,10 +1150,6 @@ const SettingPage: FC = ({ user, setUser }) => {
: settings.theme
const activeLightThemeSlotNo = getClientActiveLightThemeSlotNo ()
const activeDarkThemeSlotNo = getClientActiveDarkThemeSlotNo ()
- const initialEditingThemeSlot = resolveDisplayedTheme ({
- themeMode,
- activeLightThemeSlotNo,
- activeDarkThemeSlotNo }, completeThemeSlots).selection
setSavedThemeMode (themeMode)
setDraftThemeMode (themeMode)
@@ -1141,8 +1159,6 @@ const SettingPage: FC = ({ user, setUser }) => {
setDraftActiveDarkThemeSlotNo (activeDarkThemeSlotNo)
setSavedThemeSlots (completeThemeSlots)
setDraftThemeSlots (completeThemeSlots)
- setSavedEditingThemeSlot (initialEditingThemeSlot)
- setEditingThemeSlot (initialEditingThemeSlot)
setCachedUserThemeSlots (completeThemeSlots)
applyThemeAppearanceState ({ themeMode,
activeLightThemeSlotNo,
@@ -1218,6 +1234,16 @@ const SettingPage: FC = ({ user, setUser }) => {
discard: handleThemeDiscard })
}, [hasUnsavedThemeChanges, savedThemeSlots])
+ useEffect (() => {
+ registerUnsavedChangesSource ({
+ dirty: hasPageUnsavedChanges,
+ discard: discardAllDirtyChanges })
+
+ return () => {
+ registerUnsavedChangesSource (null)
+ }
+ }, [discardAllDirtyChanges, hasPageUnsavedChanges, registerUnsavedChangesSource])
+
useKeyboardShortcuts ({
['settings.account']: () => {
requestActiveTab ('account')
@@ -1236,7 +1262,7 @@ const SettingPage: FC = ({ user, setUser }) => {
draftThemeMode,
draftActiveLightThemeSlotNo,
draftActiveDarkThemeSlotNo,
- editingThemeSlot,
+ currentDisplayedThemeSlot: displayedThemeSelection.selection,
selectedTheme,
displayedThemeLabel: themeLabelBySelection (displayedThemeSelection.selection),
systemThemeLabel: displayedThemeSelection.baseTheme === 'dark' ? 'ダーク' : 'ライト',
@@ -1244,36 +1270,27 @@ const SettingPage: FC = ({ user, setUser }) => {
settingsBaseErrors,
settingsFieldErrors,
onSelectThemeMode: themeMode => {
- requestThemeStateChange (baseState => ({ ...baseState, themeMode }))
+ requestThemeMode (themeMode)
},
onSelectLightThemeSlotNo: slotNo => {
- requestThemeStateChange (baseState => ({
- ...baseState,
- activeLightThemeSlotNo: slotNo,
- editingThemeSlot: getUserThemeSlotSelection ('light', slotNo) }))
+ requestLightThemeSlotNo (slotNo)
},
onSelectDarkThemeSlotNo: slotNo => {
- requestThemeStateChange (baseState => ({
- ...baseState,
- activeDarkThemeSlotNo: slotNo,
- editingThemeSlot: getUserThemeSlotSelection ('dark', slotNo) }))
- },
- onSelectEditingThemeSlot: selection => {
- requestThemeStateChange (baseState => ({ ...baseState, editingThemeSlot: selection }))
+ requestDarkThemeSlotNo (slotNo)
},
onThemeTokenChange: (key, value) => {
- updateDraftThemeSlot (editingThemeSlot, tokens => ({ ...tokens, [key]: value }))
+ updateDraftThemeSlot (tokens => ({ ...tokens, [key]: value }))
},
onTagColourChange: (category, colour) => {
- updateDraftThemeSlot (editingThemeSlot, tokens => ({
+ updateDraftThemeSlot (tokens => ({
...tokens, tagColours: { ...tokens.tagColours, [category]: colour } }))
},
- onResetThemeSlot: selection => {
+ onResetThemeSlot: () => {
+ const selection = displayedThemeSelection.selection
const baseTheme =
draftThemeSlots[selection]?.baseTheme
?? selection.split (':')[1] as BuiltinThemeId
updateDraftThemeSlot (
- selection,
() => getDefaultUserThemeSlot (
baseTheme,
(draftThemeSlots[selection]?.slotNo
@@ -1313,7 +1330,7 @@ const SettingPage: FC = ({ user, setUser }) => {
: ['border-border bg-background text-foreground']),
)}
onClick={() => {
- void requestActiveTab (tab.id)
+ requestActiveTab (tab.id)
}}>
{tab.label}
{settingsTabErrors[tab.id] ? ' !' : ''}
@@ -1367,7 +1384,7 @@ const SettingPage: FC = ({ user, setUser }) => {
'dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100']),
)}
onClick={() => {
- void requestActiveTab (tab.id)
+ requestActiveTab (tab.id)
}}
onKeyDown={handleTabKeyDown}>
{tab.label}