Files
Modrinth-Enhanced/patches/0005-Make-the-sidebars-foldable.patch
T

270 lines
11 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
Date: Mon, 14 Sep 2026 13:06:38 +0200
Subject: [PATCH] Make the sidebars foldable
Three sidebar preferences, all stored as feature flags so they survive a
restart without needing a settings migration:
* The Modrinth Servers button in the left sidebar can be switched off
under Settings > Features > Sidebar. It stays on by default.
* The news section in the right sidebar collapses by clicking its
heading, remembering the state the way the friends list does.
* The button that folds the right sidebar away is always in the title
bar, rather than appearing only once "Hide right sidebar" is turned on
in settings, and what it is set to is remembered. That setting still
works, and still seeds the state. Its label was pointing at the wrong
message ("Next image"), which is fixed here too.
None of these three reach Modrinth. Preference syncing maps a fixed list
of named fields in both directions, and these are not in it, so they are
neither sent nor overwritten by another device.
The Modrinth Servers switch says so where it sits, because it sits on a
page headed "Sync features across devices" and would otherwise look like
it syncs. The other two are not settings entries - a heading and a title
bar button - so nothing there promises anything.
---
apps/app-frontend/src/App.vue | 67 ++++++++++++++++---
.../ui/settings/display/FeaturesSettings.vue | 28 ++++++++
.../src/composables/use-app-settings.ts | 3 +
packages/app-lib/src/state/settings.rs | 3 +
4 files changed, 92 insertions(+), 9 deletions(-)
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
index e5c6fd4..a9e3945 100644
--- a/apps/app-frontend/src/App.vue
+++ b/apps/app-frontend/src/App.vue
@@ -10,6 +10,7 @@ import {
} from '@modrinth/api-client'
import {
ArrowLeftRightIcon,
+ ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
CompassIcon,
@@ -254,12 +255,31 @@ const credentials = ref()
const storedModrinthAccounts = ref([])
let credentialsRefreshId = 0
const sidebarToggled = ref(true)
+// Whether the right sidebar is folded away, remembered across restarts in the
+// feature flags. The title bar button below is what normally sets it; the
+// "Hide right sidebar" setting still seeds it, so turning that on folds the
+// sidebar away as it always did.
watch(
- () => appSettings.toggleSidebar,
- (toggleSidebar) => {
- sidebarToggled.value = !toggleSidebar
+ () => appSettings.getFeatureFlag('right_sidebar_collapsed'),
+ (collapsed) => {
+ sidebarToggled.value = !collapsed
},
+ { immediate: true },
)
+watch(
+ () => appSettings.toggleSidebar,
+ (hide) => setSidebarCollapsed(hide),
+)
+
+function setSidebarCollapsed(collapsed) {
+ appSettings.featureFlags.right_sidebar_collapsed = collapsed
+ getSettings()
+ .then((settings) => {
+ settings.feature_flags.right_sidebar_collapsed = collapsed
+ return setSettings(settings)
+ })
+ .catch(handleError)
+}
const forceSidebar = computed(
() =>
route.path.startsWith('/browse') ||
@@ -358,6 +378,21 @@ useQuery({
// ad and the gradient being drawn above it.
const showAd = computed(() => false)
const adConsentAvailable = computed(() => false)
+
+// Whether the news section in the right sidebar is folded away. Kept in the
+// feature flags so it survives a restart, the same way the friends list
+// remembers its collapsed sections.
+const newsCollapsed = computed(() => appSettings.getFeatureFlag('news_collapsed'))
+
+function setNewsCollapsed(collapsed) {
+ appSettings.featureFlags.news_collapsed = collapsed
+ getSettings()
+ .then((settings) => {
+ settings.feature_flags.news_collapsed = collapsed
+ return setSettings(settings)
+ })
+ .catch(handleError)
+}
providePageContext({
hierarchicalSidebarAvailable: ref(true),
showAds: showAd,
@@ -588,6 +623,8 @@ const messages = defineMessages({
goBack: { id: 'app.navigation.go-back', defaultMessage: 'Go back' },
goForward: { id: 'app.navigation.go-forward', defaultMessage: 'Go forward' },
nextImage: { id: 'app.navigation.next-image', defaultMessage: 'Next image' },
+ hideSidebar: { id: 'app.navigation.hide-sidebar', defaultMessage: 'Hide sidebar' },
+ showSidebar: { id: 'app.navigation.show-sidebar', defaultMessage: 'Show sidebar' },
updateDownloadMissingVersion: {
id: 'app.update.download-error.missing-version',
defaultMessage: 'Failed to download update: no version available',
@@ -2252,6 +2289,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<ImageIcon />
</NavButton>
<NavButton
+ v-if="appSettings.getFeatureFlag('show_hosting_in_sidebar')"
v-tooltip.right="formatMessage(messages.modrinthHosting)"
to="/hosting/manage"
:is-primary="(r) => r.path === '/hosting/manage' || r.path === '/hosting/manage/'"
@@ -2378,12 +2416,15 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</div>
<section data-tauri-drag-region class="flex shrink-0 ml-auto items-center">
<IconButton
- v-if="!forceSidebar && appSettings.toggleSidebar"
+ v-if="!forceSidebar"
+ v-tooltip="
+ formatMessage(sidebarToggled ? messages.hideSidebar : messages.showSidebar)
+ "
:type="sidebarToggled ? 'base' : 'quiet'"
- :label="formatMessage(messages.nextImage)"
+ :label="formatMessage(sidebarToggled ? messages.hideSidebar : messages.showSidebar)"
class="mr-3 transition-transform"
:class="{ 'rotate-180': !sidebarToggled }"
- @click="sidebarToggled = !sidebarToggled"
+ @click="setSidebarCollapsed(sidebarToggled)"
>
<RightArrowIcon />
</IconButton>
@@ -2502,10 +2543,18 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid"
/>
<div v-if="news && news.length > 0" class="p-4 flex flex-col items-center">
- <h3 class="text-base mb-4 text-primary font-medium m-0 text-left w-full">
+ <button
+ class="button-base m-0 mb-4 flex w-full cursor-pointer items-center justify-between gap-2 border-0 bg-transparent p-0 text-left text-base font-medium text-primary"
+ :aria-expanded="!newsCollapsed"
+ @click="setNewsCollapsed(!newsCollapsed)"
+ >
{{ formatMessage(messages.news) }}
- </h3>
- <div class="space-y-4 flex flex-col items-center w-full">
+ <ChevronDownIcon
+ class="h-5 w-5 shrink-0 transition-transform"
+ :class="{ '-rotate-90': newsCollapsed }"
+ />
+ </button>
+ <div v-if="!newsCollapsed" class="space-y-4 flex flex-col items-center w-full">
<NewsArticleCard
v-for="(item, index) in news"
:key="`news-${index}`"
diff --git a/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue b/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue
index 1d3e379..ae88550 100644
--- a/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue
+++ b/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue
@@ -43,6 +43,7 @@ const quickInstances = useQuickInstanceLimit()
const queryClient = useQueryClient()
const showJumpInFlag: FeatureFlag = 'worlds_in_home'
+const showHostingFlag: FeatureFlag = 'show_hosting_in_sidebar'
const messages = defineMessages({
syncAcrossDevicesTitle: {
@@ -95,6 +96,15 @@ const messages = defineMessages({
id: 'app.features-settings.show-skin-selector.description',
defaultMessage: 'Show a button in the left sidebar to open the skin selector.',
},
+ showHostingTitle: {
+ id: 'app.features-settings.show-hosting.title',
+ defaultMessage: 'Show Modrinth Servers in sidebar',
+ },
+ showHostingDescription: {
+ id: 'app.features-settings.show-hosting.description',
+ defaultMessage:
+ 'Show a button in the left sidebar to manage Modrinth Servers. Added by Modrinth Enhanced, so it stays on this device and is not synced to your Modrinth account.',
+ },
quickInstancesTitle: {
id: 'app.features-settings.quick-instances.title',
defaultMessage: 'Quick instances in sidebar',
@@ -125,6 +135,7 @@ type FeaturesSettingsState = {
showScreenshotsTab: boolean
showAllScreenshots: boolean
showSkinSelector: boolean
+ showHosting: boolean
quickInstanceCount: number
showJumpIn: boolean
}
@@ -144,6 +155,7 @@ function getFeaturesSettingsState(
showScreenshotsTab: settings.show_screenshots_tab_in_instances,
showAllScreenshots: globalSyncedOptions.screenshots,
showSkinSelector: settings.show_skin_selector_in_sidebar,
+ showHosting: settings.feature_flags[showHostingFlag] ?? DEFAULT_FEATURE_FLAGS[showHostingFlag],
quickInstanceCount: quickInstances.limit.value ?? QUICK_INSTANCE_LIMIT_MAX,
showJumpIn: settings.feature_flags[showJumpInFlag] ?? DEFAULT_FEATURE_FLAGS[showJumpInFlag],
}
@@ -184,6 +196,7 @@ const settingsMutation = useMutation({
feature_flags: {
...latestSettings.feature_flags,
[showJumpInFlag]: value.showJumpIn,
+ [showHostingFlag]: value.showHosting,
},
}
@@ -206,6 +219,7 @@ const settingsMutation = useMutation({
appSettings.showScreenshotsTabInInstances = value.showScreenshotsTab
appSettings.showSkinSelectorInSidebar = value.showSkinSelector
appSettings.featureFlags[showJumpInFlag] = value.showJumpIn
+ appSettings.featureFlags[showHostingFlag] = value.showHosting
if (updateQuickInstanceCount) {
quickInstances.setLimit(value.quickInstanceCount)
@@ -366,6 +380,20 @@ onBeforeUnmount(() => {
/>
</div>
+ <div class="flex items-center justify-between gap-4">
+ <div>
+ <h3 class="m-0 text-lg font-semibold text-contrast">
+ {{ formatMessage(messages.showHostingTitle) }}
+ </h3>
+ <p class="m-0 mt-1">{{ formatMessage(messages.showHostingDescription) }}</p>
+ </div>
+ <Toggle
+ id="show-hosting-in-sidebar"
+ v-model="current.showHosting"
+ :aria-label="formatMessage(messages.showHostingTitle)"
+ />
+ </div>
+
<div class="flex flex-col gap-2.5">
<h3 class="m-0 text-lg font-semibold text-contrast">
{{ formatMessage(messages.quickInstancesTitle) }}
diff --git a/apps/app-frontend/src/composables/use-app-settings.ts b/apps/app-frontend/src/composables/use-app-settings.ts
index 0d58903..59da62b 100644
--- a/apps/app-frontend/src/composables/use-app-settings.ts
+++ b/apps/app-frontend/src/composables/use-app-settings.ts
@@ -24,6 +24,9 @@ export const DEFAULT_FEATURE_FLAGS = {
friends_pending_collapsed: true,
dismissed_photosensitivity_filter_warning: false,
localhost_sign_in: false,
+ show_hosting_in_sidebar: true,
+ news_collapsed: false,
+ right_sidebar_collapsed: false,
}
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
diff --git a/packages/app-lib/src/state/settings.rs b/packages/app-lib/src/state/settings.rs
index 20601b8..2fd0d87 100644
--- a/packages/app-lib/src/state/settings.rs
+++ b/packages/app-lib/src/state/settings.rs
@@ -88,6 +88,9 @@ pub enum FeatureFlag {
FriendsPendingCollapsed,
DismissedPhotosensitivityFilterWarning,
LocalhostSignIn,
+ ShowHostingInSidebar,
+ NewsCollapsed,
+ RightSidebarCollapsed,
}
impl Settings {