From e2cb25e2e1920c3fbcee92b23d957b12727047bf Mon Sep 17 00:00:00 2001 From: SRHTNIN Date: Tue, 13 Oct 2026 17:16:03 +0200 Subject: [PATCH] Web settings in sections Co-Authored-By: Claude Opus 5.5 --- Frontend/src/Components/SettingsPanel.jsx | 808 ++++++++++++---------- Frontend/src/Styles/Theme.css | 77 ++- 2 files changed, 509 insertions(+), 376 deletions(-) diff --git a/Frontend/src/Components/SettingsPanel.jsx b/Frontend/src/Components/SettingsPanel.jsx index 2fc5000..7f400cb 100644 --- a/Frontend/src/Components/SettingsPanel.jsx +++ b/Frontend/src/Components/SettingsPanel.jsx @@ -74,6 +74,12 @@ export default function SettingsPanel({ const [ExpandedGroup, SetExpandedGroup] = useState(null); const [ConfirmingDeleteGroupName, SetConfirmingDeleteGroupName] = useState(null); + // Sections down the left, like the other SaraSuite programs' settings. + const [Category, SetCategory] = useState("General"); + const Categories = ["General", "Editor", "Appearance", "Graph", "Profile", "Account"].concat( + CurrentUser && CurrentUser.IsAdmin ? ["Users", "Groups", "Locked directories"] : [], + ); + useEffect(() => { FetchSettings().then(SetSettings); FetchThemeNames().then(SetThemeNames); @@ -316,402 +322,458 @@ export default function SettingsPanel({ -
-
- - UpdateField("FontFamily", Event.target.value)} - /> -
- -
- - UpdateField("FontSize", Number(Event.target.value))} - /> -
- -
- - UpdateField("ShowHiddenFolders", Event.target.checked)} /> -
- -
- - UpdateField("ShowSvFiles", Event.target.checked)} /> -
- -
- - UpdateField("ShowBreadcrumbs", Event.target.checked)} /> -
- -
- - UpdateField("UseHyphenBullets", Event.target.checked)} /> -
- -
- - UpdateField("UseAsciiFolderIcons", Event.target.checked)} /> -
- -
- - UpdateField("NavigateIntoDirectories", Event.target.checked)} /> -
- -
- - UpdateField("NewEntryButtonsAtTop", Event.target.checked)} /> -
- -
- - UpdateField("ShowGitBlame", Event.target.checked)} /> -
- -
- - UpdateField("ShowBlameAvatars", Event.target.checked)} /> -
- -
- - UpdateField("VisibleInBlame", Event.target.checked)} /> -
- -
- - UpdateField("StickyToolbar", Event.target.checked)} /> -
- -
- - -
- -
-
Graph
-
- - UpdateField("GraphShowDirectories", Event.target.checked)} - /> -
-
- - UpdateField("GraphColourByDepth", Event.target.checked)} - /> -
-
- - UpdateField("GraphShrinkByDepth", Event.target.checked)} - /> -
-
- - -
+ {Name} + + ))}
- -
- - UpdateField("ConfirmBeforeDelete", Event.target.checked)} /> -
- -
-
Colour theme
-
- -
- - -
-
- - {!IsCreatingTheme && ( - - )} - {IsCreatingTheme && NewThemePalette && ( +
+ {Category === "General" && ( <> -
- - SetNewThemeName(Event.target.value)} - placeholder="MyTheme" - /> +
+ + UpdateField("ShowHiddenFolders", Event.target.checked)} />
-
- {PaletteRoles.map((Role) => ( -
- - SetNewThemePalette({ ...NewThemePalette, [Role]: Event.target.value }) - } - /> - {Role} -
- ))} + + +
+ + UpdateField("ShowSvFiles", Event.target.checked)} />
- + + +
+ + UpdateField("ShowBreadcrumbs", Event.target.checked)} /> +
+ + +
+ + UpdateField("UseHyphenBullets", Event.target.checked)} /> +
+ + +
+ + UpdateField("UseAsciiFolderIcons", Event.target.checked)} /> +
+ + +
+ + UpdateField("NavigateIntoDirectories", Event.target.checked)} /> +
+ + +
+ + UpdateField("NewEntryButtonsAtTop", Event.target.checked)} /> +
+ + +
+ + UpdateField("ConfirmBeforeDelete", Event.target.checked)} /> +
+ )} - {ThemeError &&
{ThemeError}
} -
- -
-
Profile
-
- -
- {ColourOptions.map((Colour) => ( -
-
-
- -
- {CurrentUser && ( - { - Event.target.style.visibility = "hidden"; - }} - /> - )} - -
-
- {ProfileError &&
{ProfileError}
} -
- -
-
Account
-
- - Logged in as {CurrentUser ? CurrentUser.Username : "..."} - {CurrentUser && CurrentUser.IsAdmin ? " (admin)" : ""} - - -
- -
- -
- SetUsernameField(Event.target.value)} - /> - -
- {UsernameError &&
{UsernameError}
} -
- Renaming logs out any other devices/browsers you're signed in on - this one - stays signed in under the new name. -
-
- -
- - SetCurrentPasswordField(Event.target.value)} - /> - SetNewPasswordField(Event.target.value)} - /> - - {PasswordChanged &&
Password updated.
} - {PasswordError &&
{PasswordError}
} -
-
- - {CurrentUser && CurrentUser.IsAdmin && ( -
-
Users (admin)
- {Users && - Users.map((User) => ( -
- - - {User.Username} - {User.IsAdmin ? " (admin)" : ""} - -
- ))} -
- - SetNewUsername(Event.target.value)} - /> -
-
- - SetNewUserPassword(Event.target.value)} - /> -
-
- -
- {ColourOptions.map((Colour) => ( -
-
- - SetNewUserIsAdmin(Event.target.checked)} /> -
- {UserError &&
{UserError}
} - -
- )} - {CurrentUser && CurrentUser.IsAdmin && ( -
-
Locked Directories (admin)
- {Locks && Locks.length === 0 && ( -
- Nothing locked yet - use the 🔒 button next to any directory to lock it. + +
+ + UpdateField("ShowGitBlame", Event.target.checked)} />
- )} - {Locks && - Locks.map((Lock, Index) => ( -
- - {Lock.Root}/{Lock.Path} -{" "} - {[...(Lock.Users || []), ...(Lock.Groups || []).map((G) => `@${G}`)].join( - ", " - )} - - -
- ))} - {LockError &&
{LockError}
} -
- )} - {CurrentUser && CurrentUser.IsAdmin && ( -
-
Groups (admin)
- {Groups && - Groups.map((Group) => ( -
+ +
+ + UpdateField("ShowBlameAvatars", Event.target.checked)} /> +
+ + +
+ + UpdateField("VisibleInBlame", Event.target.checked)} /> +
+ + +
+ + UpdateField("StickyToolbar", Event.target.checked)} /> +
+ + + )} + {Category === "Appearance" && ( + <> +
+
+
- - {Group.Name} ({(Group.Members || []).length}) - -
- - {Group.Name !== "Admin" && Group.Name !== "Guest" && ( - - )} -
+ +
- {ExpandedGroup === Group.Name && ( -
- {(Users || []).map((User) => ( -
+ + {!IsCreatingTheme && ( + + )} + {IsCreatingTheme && NewThemePalette && ( + <> +
+ + SetNewThemeName(Event.target.value)} + placeholder="MyTheme" + /> +
+
+ {PaletteRoles.map((Role) => ( +
HandleToggleGroupMember(Group, User.Username)} + type="color" + value={NewThemePalette[Role] || "#000000"} + onChange={(Event) => + SetNewThemePalette({ ...NewThemePalette, [Role]: Event.target.value }) + } /> + {Role} +
+ ))} +
+ + + )} + {ThemeError &&
{ThemeError}
} +
+ + +
+ + UpdateField("FontFamily", Event.target.value)} + /> +
+ + +
+ + UpdateField("FontSize", Number(Event.target.value))} + /> +
+ + + )} + {Category === "Graph" && ( + <> +
+
+ + UpdateField("GraphShowDirectories", Event.target.checked)} + /> +
+
+ + UpdateField("GraphColourByDepth", Event.target.checked)} + /> +
+
+ + UpdateField("GraphShrinkByDepth", Event.target.checked)} + /> +
+
+ + +
+
+ + + )} + {Category === "Profile" && ( + <> +
+
+ +
+ {ColourOptions.map((Colour) => ( +
+
+
+ +
+ {CurrentUser && ( + { + Event.target.style.visibility = "hidden"; + }} + /> + )} + +
+
+ {ProfileError &&
{ProfileError}
} +
+ + + )} + {Category === "Account" && ( + <> +
+
+ + Logged in as {CurrentUser ? CurrentUser.Username : "..."} + {CurrentUser && CurrentUser.IsAdmin ? " (admin)" : ""} + + +
+ +
+ +
+ SetUsernameField(Event.target.value)} + /> + +
+ {UsernameError &&
{UsernameError}
} +
+ Renaming logs out any other devices/browsers you're signed in on - this one + stays signed in under the new name. +
+
+ +
+ + SetCurrentPasswordField(Event.target.value)} + /> + SetNewPasswordField(Event.target.value)} + /> + + {PasswordChanged &&
Password updated.
} + {PasswordError &&
{PasswordError}
} +
+
+ + + )} + {Category === "Users" && ( + <> + {CurrentUser && CurrentUser.IsAdmin && ( +
+ {Users && + Users.map((User) => ( +
+ - {User.Username} - + {User.Username} + {User.IsAdmin ? " (admin)" : ""} + +
+ ))} +
+ + SetNewUsername(Event.target.value)} + /> +
+
+ + SetNewUserPassword(Event.target.value)} + /> +
+
+ +
+ {ColourOptions.map((Colour) => ( +
- )} +
+
+ + SetNewUserIsAdmin(Event.target.checked)} /> +
+ {UserError &&
{UserError}
} +
- ))} -
- SetNewGroupName(Event.target.value)} - /> - -
- {GroupError &&
{GroupError}
} -
- )} + )} - {SaveError &&
{SaveError}
} + + )} + {Category === "Groups" && ( + <> + {CurrentUser && CurrentUser.IsAdmin && ( +
+ {Groups && + Groups.map((Group) => ( +
+
+ + {Group.Name} ({(Group.Members || []).length}) + +
+ + {Group.Name !== "Admin" && Group.Name !== "Guest" && ( + + )} +
+
+ {ExpandedGroup === Group.Name && ( +
+ {(Users || []).map((User) => ( + + ))} +
+ )} +
+ ))} +
+ SetNewGroupName(Event.target.value)} + /> + +
+ {GroupError &&
{GroupError}
} +
+ )} + + + )} + {Category === "Locked directories" && ( + <> + {CurrentUser && CurrentUser.IsAdmin && ( +
+ {Locks && Locks.length === 0 && ( +
+ Nothing locked yet - use the 🔒 button next to any directory to lock it. +
+ )} + {Locks && + Locks.map((Lock, Index) => ( +
+ + {Lock.Root}/{Lock.Path} -{" "} + {[...(Lock.Users || []), ...(Lock.Groups || []).map((G) => `@${G}`)].join( + ", " + )} + + +
+ ))} + {LockError &&
{LockError}
} +
+ )} + + + )} + + {SaveError &&
{SaveError}
} +
diff --git a/Frontend/src/Styles/Theme.css b/Frontend/src/Styles/Theme.css index 83324a2..07ad4ce 100644 --- a/Frontend/src/Styles/Theme.css +++ b/Frontend/src/Styles/Theme.css @@ -983,9 +983,10 @@ img, } .SettingsModal { - width: 420px; - max-height: 80vh; - overflow-y: auto; + width: min(700px, calc(100vw - 32px)); + height: min(560px, 85vh); + display: flex; + flex-direction: column; background: var(--Surface); border: 1px solid var(--Purple); border-radius: 8px; @@ -1020,6 +1021,76 @@ img, gap: 16px; } +/* Sections down the left, the open one on the right (like the other + SaraSuite programs' settings); stacked on narrow screens. */ +.SettingsSplit { + flex: 1; + min-height: 0; + flex-direction: row; + gap: 0; + padding: 0; +} + +.SettingsCategories { + flex: 0 0 150px; + display: flex; + flex-direction: column; + gap: 2px; + padding: 8px; + background: var(--SurfaceAlt); + overflow-y: auto; +} + +.SettingsCategories .SettingsCategory { + text-align: left; + background: none; + border: none; + border-radius: 4px; + padding: 6px 10px; + color: var(--Text); + font-family: inherit; + font-size: inherit; + cursor: pointer; +} + +.SettingsCategories .SettingsCategory:hover { + background: var(--Surface); +} + +/* Beats .SettingsBody button. */ +.SettingsCategories .SettingsCategoryActive, +.SettingsCategories .SettingsCategoryActive:hover { + background: var(--Border); + color: var(--Purple); +} + +.SettingsDetail { + flex: 1; + min-width: 0; + overflow-y: auto; + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.SettingsDetail > .SettingsSection:first-child { + border-top: none; + padding-top: 0; +} + +@media (max-width: 640px) { + .SettingsSplit { + flex-direction: column; + } + + .SettingsCategories { + flex: 0 0 auto; + flex-direction: row; + flex-wrap: wrap; + } +} + .SettingsRow { display: flex; flex-direction: column;