mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 09:04:55 +00:00
feat: Allow for adjustable alignment of main content on certain pages for content moderators (#7034)
* Add type safety and validation for moderation settings * Add ability for moderators to adjust page layout for discover, project, moderation pages Primarily for changes to have more tooling present without overlapping important info
This commit is contained in:
@@ -111,6 +111,18 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.align-x {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&.align-l {
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
|
||||
&.align-r {
|
||||
margin: 0 auto 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.normal-page__sidebar {
|
||||
@@ -140,6 +152,18 @@
|
||||
'sidebar'
|
||||
/ 100%;
|
||||
|
||||
&.align-x {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&.align-l {
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
|
||||
&.align-r {
|
||||
margin: 0 auto 0 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
&.sidebar {
|
||||
grid-template:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<ProjectBackgroundGradient :project="project" />
|
||||
</Teleport>
|
||||
<template v-if="isSettings">
|
||||
<div v-if="canAccessSettings" class="normal-page no-sidebar">
|
||||
<div v-if="canAccessSettings" class="normal-page no-sidebar" :class="`align-${marginTarget}`">
|
||||
<div class="normal-page__header">
|
||||
<div
|
||||
class="mb-4 flex flex-wrap items-center gap-x-2 gap-y-3 border-0 border-b-[1px] border-solid border-divider pb-4 text-lg font-semibold"
|
||||
@@ -94,7 +94,8 @@
|
||||
/>
|
||||
<div
|
||||
class="new-page sidebar"
|
||||
:class="{
|
||||
:class="[
|
||||
{
|
||||
'alt-layout': cosmetics.leftContentLayout,
|
||||
'checklist-open':
|
||||
showModerationChecklist &&
|
||||
@@ -104,7 +105,9 @@
|
||||
showModerationChecklist &&
|
||||
collapsedModerationChecklist &&
|
||||
!flags.alwaysShowChecklistAsPopup,
|
||||
}"
|
||||
},
|
||||
`align-${marginTarget}`,
|
||||
]"
|
||||
>
|
||||
<div
|
||||
class="normal-page__header relative mb-4"
|
||||
@@ -511,7 +514,7 @@ import {
|
||||
SettingsIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { moderationSettings } from '@modrinth/moderation'
|
||||
import { getMarginTarget, moderationSettings } from '@modrinth/moderation'
|
||||
import {
|
||||
Admonition,
|
||||
Avatar,
|
||||
@@ -583,6 +586,7 @@ const config = useRuntimeConfig()
|
||||
const moderationQueue = useModerationQueue()
|
||||
const keybinds = useModerationKeybinds()
|
||||
const modSettings = useModerationSettings()
|
||||
const marginTarget = computed(() => getMarginTarget(modSettings.value))
|
||||
const notifications = injectNotificationManager()
|
||||
const { addNotification } = notifications
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { getMarginTarget } from '@modrinth/moderation'
|
||||
import { commonProjectTypeCategoryMessages, NavTabs, useVIntl } from '@modrinth/ui'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const flags = useFeatureFlags()
|
||||
const route = useRoute()
|
||||
const modSettings = useModerationSettings()
|
||||
|
||||
const allowTabChanging = computed(() => !route.query.sid)
|
||||
const marginTarget = computed(() => getMarginTarget(modSettings.value))
|
||||
|
||||
const selectableProjectTypes = [
|
||||
{
|
||||
@@ -47,7 +50,10 @@ const selectableProjectTypes = [
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="mx-auto box-border flex w-full max-w-[1280px] flex-col gap-4 px-6 pb-6">
|
||||
<div
|
||||
class="box-border flex w-full max-w-[1280px] flex-col gap-4 px-6 pb-6"
|
||||
:class="`m${marginTarget}-auto`"
|
||||
>
|
||||
<NavTabs
|
||||
v-if="!flags.projectTypesPrimaryNav && allowTabChanging"
|
||||
:links="selectableProjectTypes"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="relative mx-auto mb-6 flex min-h-screen w-full max-w-[1280px] flex-col px-6">
|
||||
<div
|
||||
class="relativemb-6 flex min-h-screen w-full max-w-[1280px] flex-col px-6"
|
||||
:class="`m${marginTarget}-auto`"
|
||||
>
|
||||
<h1>Moderation</h1>
|
||||
<NavTabs :links="moderationLinks" class="mb-4 hidden sm:flex" />
|
||||
<div class="mb-4 sm:hidden">
|
||||
@@ -16,6 +19,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FolderIcon, GlobeIcon, HashIcon, ReportIcon, ShieldCheckIcon } from '@modrinth/assets'
|
||||
import { getMarginTarget } from '@modrinth/moderation'
|
||||
import { Chips, defineMessages, NavTabs, useVIntl } from '@modrinth/ui'
|
||||
|
||||
definePageMeta({
|
||||
@@ -30,6 +34,9 @@ const { formatMessage } = useVIntl()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const modSettings = useModerationSettings()
|
||||
const marginTarget = computed(() => getMarginTarget(modSettings.value))
|
||||
|
||||
const messages = defineMessages({
|
||||
projectsTitle: {
|
||||
id: 'moderation.page.projects',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { EnumSettingDefinition, ToggleSettingDefinition } from '../types/settings.ts'
|
||||
import { setting } from '../types/settings.ts'
|
||||
|
||||
const settings = {
|
||||
General: {
|
||||
ChecklistPosition: {
|
||||
ChecklistPosition: setting.asEnum({
|
||||
type: 'enum',
|
||||
id: 'checklist-position',
|
||||
title: 'Checklist Position',
|
||||
@@ -12,21 +12,34 @@ const settings = {
|
||||
{ value: 'right', label: 'Right' },
|
||||
],
|
||||
default: 'right',
|
||||
} as EnumSettingDefinition,
|
||||
ProjectKeybinds: {
|
||||
}),
|
||||
ProjectKeybinds: setting.asToggle({
|
||||
type: 'toggle',
|
||||
id: 'project-keybinds',
|
||||
title: 'Enable Project Keybinds',
|
||||
description: 'Weather certain keybinds should work without the checklist visible.',
|
||||
default: false,
|
||||
} as ToggleSettingDefinition,
|
||||
PrivateMessageHighlight: {
|
||||
}),
|
||||
PrivateMessageHighlight: setting.asToggle({
|
||||
type: 'toggle',
|
||||
id: 'private-message-highlight',
|
||||
title: 'Highlight Private Messages',
|
||||
description: 'Whether private messages should be highlighted in the chat.',
|
||||
default: true,
|
||||
} as ToggleSettingDefinition,
|
||||
}),
|
||||
AdjustPageAlignment: setting.asEnum({
|
||||
type: 'enum',
|
||||
id: 'adjust-page-alignment',
|
||||
title: 'Adjust Page Alignment',
|
||||
description:
|
||||
'Whether the main page elements should be centered or offset opposite to the Checklist Position.',
|
||||
entries: [
|
||||
{ value: 'never', label: 'Never' },
|
||||
{ value: 'checklist-present', label: 'Checklist Needed' },
|
||||
{ value: 'always', label: 'Always' },
|
||||
],
|
||||
default: 'never',
|
||||
}),
|
||||
},
|
||||
} as const
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SettingDefinitionBase } from '../types/settings.ts'
|
||||
import { isValidFor, type SettingDefinitionBase } from '../types/settings.ts'
|
||||
import { moderationSettings } from '../index.ts'
|
||||
|
||||
export class Settings {
|
||||
private readonly settings: { [id: string]: any }
|
||||
@@ -13,7 +14,9 @@ export class Settings {
|
||||
}
|
||||
|
||||
get<T>(definition: SettingDefinitionBase<T>): T {
|
||||
return this.settings[definition.id] ?? definition.default
|
||||
const value = this.settings[definition.id]
|
||||
|
||||
return (isValidFor(definition, value) ? value : undefined) ?? definition.default
|
||||
}
|
||||
|
||||
set<T>(definition: SettingDefinitionBase<T>, value?: T): void {
|
||||
@@ -23,3 +26,11 @@ export class Settings {
|
||||
this.onChange()
|
||||
}
|
||||
}
|
||||
|
||||
export function getMarginTarget(settings: Settings): string {
|
||||
return settings.get(moderationSettings.General.AdjustPageAlignment) == 'always'
|
||||
? settings.get(moderationSettings.General.ChecklistPosition) == 'right'
|
||||
? 'r'
|
||||
: 'l'
|
||||
: 'x'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface SettingDefinitionBase<T> {
|
||||
type: string
|
||||
type: SettingDefinitionTypes
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
@@ -11,16 +11,53 @@ export interface ToggleSettingDefinition extends SettingDefinitionBase<boolean>
|
||||
type: 'toggle'
|
||||
}
|
||||
|
||||
export interface EnumSettingDefinition extends SettingDefinitionBase<string> {
|
||||
export interface EnumSettingDefinition<T extends string = string> extends SettingDefinitionBase<T> {
|
||||
type: 'enum'
|
||||
entries: {
|
||||
label: string
|
||||
value: string
|
||||
value: T
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface StringSettingDefinition extends SettingDefinitionBase<string> {
|
||||
type: 'string'
|
||||
regex?: RegExp
|
||||
}
|
||||
|
||||
export type SettingDefinitions = ReturnType<(typeof setting)[keyof typeof setting]>
|
||||
export type SettingDefinitionTypes = SettingDefinitions['type']
|
||||
|
||||
export const setting = {
|
||||
asEnum: <const E extends ReadonlyArray<{ label: string; value: string }>>(
|
||||
data: EnumSettingDefinition<E[number]['value']> & { entries: E },
|
||||
): EnumSettingDefinition<E[number]['value']> => {
|
||||
return data as EnumSettingDefinition<E[number]['value']>
|
||||
},
|
||||
asToggle: (data: ToggleSettingDefinition) => data,
|
||||
asString: (data: StringSettingDefinition) => data,
|
||||
}
|
||||
|
||||
export function isValidFor(definitionBase: SettingDefinitionBase<any>, value: any): boolean {
|
||||
if (value != null) {
|
||||
// Tried my best with type safety but sadly having the `SettingDefinitions` as the type leads to issues handling types else where...
|
||||
const definition = definitionBase as SettingDefinitions
|
||||
if (
|
||||
definition.type == 'enum' &&
|
||||
definition.entries.map((entry) => entry.value).includes(value)
|
||||
) {
|
||||
return true
|
||||
} else if (definition.type == 'toggle' && typeof value === 'boolean') {
|
||||
return true
|
||||
} else if (
|
||||
definition.type == 'string' &&
|
||||
typeof value === 'string' &&
|
||||
(!definition.regex || value.match(definition.regex))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export type SettingDefinition =
|
||||
|
||||
Reference in New Issue
Block a user