mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-10-27 08:14:30 -04:00
feat: consolidate settings gui (#6043)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
absolute
|
||||
:top-link="topLinks"
|
||||
:secondary-links="cookbookLinks || []"
|
||||
:bottom-links="bottomLinks"
|
||||
>
|
||||
<v-menu
|
||||
offset-y
|
||||
@@ -84,25 +83,6 @@
|
||||
</template>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<template #bottom>
|
||||
<v-list-item @click.stop="languageDialog = true">
|
||||
<template #prepend>
|
||||
<v-icon>{{ $globals.icons.translate }}</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ $t("sidebar.language") }}</v-list-item-title>
|
||||
<LanguageDialog v-model="languageDialog" />
|
||||
</v-list-item>
|
||||
<v-list-item @click="toggleDark">
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
{{ $vuetify.theme.current.dark ? $globals.icons.weatherSunny : $globals.icons.weatherNight }}
|
||||
</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
{{ $vuetify.theme.current.dark ? $t("settings.theme.light-mode") : $t("settings.theme.dark-mode") }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</AppSidebar>
|
||||
<v-main class="pt-12">
|
||||
<v-scroll-x-transition>
|
||||
@@ -121,7 +101,6 @@ import { useAppInfo } from "~/composables/api";
|
||||
import { useCookbookPreferences } from "~/composables/use-users/preferences";
|
||||
import { useCookbookStore, usePublicCookbookStore } from "~/composables/store/use-cookbook-store";
|
||||
import { useHouseholdStore, usePublicHouseholdStore } from "~/composables/store/use-household-store";
|
||||
import { useToggleDarkMode } from "~/composables/use-utils";
|
||||
import type { ReadCookBook } from "~/lib/api/types/cookbook";
|
||||
import type { HouseholdSummary } from "~/lib/api/types/household";
|
||||
|
||||
@@ -133,7 +112,6 @@ export default defineNuxtComponent({
|
||||
const $auth = useMealieAuth();
|
||||
const { isOwnGroup } = useLoggedInState();
|
||||
|
||||
const isAdmin = computed(() => $auth.user.value?.admin);
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
|
||||
|
||||
@@ -191,8 +169,6 @@ export default defineNuxtComponent({
|
||||
const appInfo = useAppInfo();
|
||||
const showImageImport = computed(() => appInfo.value?.enableOpenaiImageServices);
|
||||
|
||||
const toggleDark = useToggleDarkMode();
|
||||
|
||||
const languageDialog = ref<boolean>(false);
|
||||
|
||||
const sidebar = ref<boolean>(false);
|
||||
@@ -286,19 +262,6 @@ export default defineNuxtComponent({
|
||||
},
|
||||
]);
|
||||
|
||||
const bottomLinks = computed<SideBarLink[]>(() =>
|
||||
isAdmin.value
|
||||
? [
|
||||
{
|
||||
icon: $globals.icons.cog,
|
||||
title: i18n.t("general.settings"),
|
||||
to: "/admin/site-settings",
|
||||
restricted: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
);
|
||||
|
||||
const topLinks = computed<SideBarLink[]>(() => [
|
||||
{
|
||||
icon: $globals.icons.silverwareForkKnife,
|
||||
@@ -367,11 +330,9 @@ export default defineNuxtComponent({
|
||||
groupSlug,
|
||||
cookbookLinks,
|
||||
createLinks,
|
||||
bottomLinks,
|
||||
topLinks,
|
||||
isOwnGroup,
|
||||
languageDialog,
|
||||
toggleDark,
|
||||
sidebar,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<v-navigation-drawer v-model="showDrawer" class="d-flex flex-column d-print-none position-fixed" touchless>
|
||||
<LanguageDialog v-model="languageDialog" />
|
||||
<!-- User Profile -->
|
||||
<template v-if="loggedIn">
|
||||
<v-list-item lines="two" :to="userProfileLink" exact>
|
||||
@@ -82,20 +83,22 @@
|
||||
</template>
|
||||
|
||||
<!-- Bottom Navigation Links -->
|
||||
<template v-if="bottomLinks" #append>
|
||||
<v-list v-model:selected="bottomSelected" nav density="compact">
|
||||
<template v-for="nav in bottomLinks">
|
||||
<div v-if="!nav.restricted || isOwnGroup" :key="nav.key || nav.title">
|
||||
<v-list-item :key="nav.key || nav.title" exact link :to="nav.to" :href="nav.href"
|
||||
:target="nav.href ? '_blank' : null">
|
||||
<template #prepend>
|
||||
<v-icon>{{ nav.icon }}</v-icon>
|
||||
<template #append>
|
||||
<v-list v-model:selected="bottomSelected" nav density="comfortable">
|
||||
<v-menu location="end bottom" :offset="15">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item v-bind="props" :prepend-icon="$globals.icons.cog" :title="$t('general.settings')" />
|
||||
</template>
|
||||
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</div>
|
||||
</template>
|
||||
<slot name="bottom" />
|
||||
<v-list density="comfortable" color="primary">
|
||||
<v-list-item :prepend-icon="$globals.icons.translate" :title="$t('sidebar.language')" @click="languageDialog=true" />
|
||||
<v-list-item :prepend-icon="$vuetify.theme.current.dark ? $globals.icons.weatherSunny : $globals.icons.weatherNight" :title="$vuetify.theme.current.dark ? $t('settings.theme.light-mode') : $t('settings.theme.dark-mode')" @click="toggleDark" />
|
||||
<v-divider v-if="loggedIn" class="my-2" />
|
||||
<v-list-item v-if="loggedIn" :prepend-icon="$globals.icons.cog" :title="$t('profile.user-settings')" to="/user/profile" />
|
||||
<v-list-item v-if="canManage" :prepend-icon="$globals.icons.manageData" :title="$t('data-pages.data-management')" to="/group/data" />
|
||||
<v-divider v-if="isAdmin" class="my-2" />
|
||||
<v-list-item v-if="isAdmin" :prepend-icon="$globals.icons.wrench" :title="$t('settings.admin-settings')" to="/admin/site-settings" />
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-list>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
@@ -106,6 +109,7 @@ import { useWindowSize } from "@vueuse/core";
|
||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||
import type { SidebarLinks } from "~/types/application-types";
|
||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
||||
import { useToggleDarkMode } from "~/composables/use-utils";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: {
|
||||
@@ -130,26 +134,26 @@ export default defineNuxtComponent({
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
bottomLinks: {
|
||||
type: Array as () => SidebarLinks,
|
||||
required: false,
|
||||
default: () => ([]),
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(props, context) {
|
||||
const $auth = useMealieAuth();
|
||||
const { loggedIn, isOwnGroup } = useLoggedInState();
|
||||
const isAdmin = computed(() => $auth.user.value?.admin);
|
||||
const canManage = computed(() => $auth.user.value?.canManage);
|
||||
|
||||
const userFavoritesLink = computed(() => $auth.user.value ? `/user/${$auth.user.value.id}/favorites` : undefined);
|
||||
const userProfileLink = computed(() => $auth.user.value ? "/user/profile" : undefined);
|
||||
|
||||
const toggleDark = useToggleDarkMode();
|
||||
|
||||
const state = reactive({
|
||||
dropDowns: {} as Record<string, boolean>,
|
||||
topSelected: null as string[] | null,
|
||||
secondarySelected: null as string[] | null,
|
||||
bottomSelected: null as string[] | null,
|
||||
hasOpenedBefore: false as boolean,
|
||||
languageDialog: false as boolean,
|
||||
});
|
||||
// model to control the drawer
|
||||
const showDrawer = computed({
|
||||
@@ -171,7 +175,7 @@ export default defineNuxtComponent({
|
||||
}
|
||||
});
|
||||
|
||||
const allLinks = computed(() => [...props.topLink, ...(props.secondaryLinks || []), ...(props.bottomLinks || [])]);
|
||||
const allLinks = computed(() => [...props.topLink, ...(props.secondaryLinks || [])]);
|
||||
function initDropdowns() {
|
||||
allLinks.value.forEach((link) => {
|
||||
state.dropDowns[link.title] = link.childrenStartExpanded || false;
|
||||
@@ -193,8 +197,11 @@ export default defineNuxtComponent({
|
||||
userProfileLink,
|
||||
showDrawer,
|
||||
loggedIn,
|
||||
isAdmin,
|
||||
canManage,
|
||||
isOwnGroup,
|
||||
sessionUser: $auth.user,
|
||||
toggleDark,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
v-model="sidebar"
|
||||
absolute
|
||||
:top-link="topLinks"
|
||||
:bottom-links="bottomLinks"
|
||||
:user="{ data: true }"
|
||||
:secondary-header="$t('sidebar.developer')"
|
||||
:secondary-links="developerLinks"
|
||||
@@ -114,13 +113,4 @@ const developerLinks: SidebarLinks = [
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const bottomLinks: SidebarLinks = [
|
||||
{
|
||||
icon: $globals.icons.heart,
|
||||
title: i18n.t("about.support"),
|
||||
href: "https://github.com/sponsors/hay-kot",
|
||||
restricted: true,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -152,6 +152,7 @@ import {
|
||||
mdiCookie,
|
||||
mdiBellPlus,
|
||||
mdiLinkVariantPlus,
|
||||
mdiTableEdit,
|
||||
} from "@mdi/js";
|
||||
|
||||
export const icons = {
|
||||
@@ -240,6 +241,7 @@ export const icons = {
|
||||
linkVariantPlus: mdiLinkVariantPlus,
|
||||
lock: mdiLock,
|
||||
logout: mdiLogout,
|
||||
manageData: mdiTableEdit,
|
||||
menu: mdiMenu,
|
||||
messageText: mdiMessageText,
|
||||
newBox: mdiNewBox,
|
||||
@@ -324,5 +326,4 @@ export const icons = {
|
||||
preserveLines: mdiText,
|
||||
preserveBlocks: mdiTextBoxOutline,
|
||||
flatten: mdiMinus,
|
||||
|
||||
};
|
||||
|
||||
@@ -25,7 +25,8 @@ test('ldap login', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Login', exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/g\/home/);
|
||||
await expect(page.getByRole('navigation')).toContainText(name);
|
||||
await expect(page.getByRole('link', { name: 'Settings' })).not.toBeVisible();
|
||||
await page.getByText('Settings', { exact: true }).click();
|
||||
await expect(page.getByRole('link', { name: 'Admin Settings' })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('ldap admin login', async ({ page }) => {
|
||||
@@ -40,7 +41,8 @@ test('ldap admin login', async ({ page }) => {
|
||||
// skip admin setup page
|
||||
await page.getByRole('link', { name: "I'm already set up, just bring me to the homepage" }).click();
|
||||
await expect(page.getByRole('navigation')).toContainText(name);
|
||||
await expect(page.getByRole('link', { name: 'Settings' })).toBeVisible();
|
||||
await page.getByText('Settings', { exact: true }).click();
|
||||
await expect(page.getByRole('link', { name: 'Admin Settings' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('oidc initial login', async ({ page }) => {
|
||||
@@ -61,7 +63,8 @@ test('oidc initial login', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Sign-in' }).click();
|
||||
await expect(page).toHaveURL(/\/g\/home/);
|
||||
await expect(page.getByRole('navigation')).toContainText(name);
|
||||
await expect(page.getByRole('link', { name: 'Settings' })).not.toBeVisible();
|
||||
await page.getByText('Settings', { exact: true }).click();
|
||||
await expect(page.getByRole('link', { name: 'Admin Settings' })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('oidc login with user not in propery group', async ({ page }) => {
|
||||
@@ -167,5 +170,6 @@ test('oidc admin user', async ({ page }) => {
|
||||
await expect(page).toHaveURL(/\/admin\/setup/, { timeout: 15000 });
|
||||
await page.getByRole('link', { name: "I'm already set up, just bring me to the homepage" }).click();
|
||||
await expect(page.getByRole('navigation')).toContainText(name);
|
||||
await expect(page.getByRole('link', { name: 'Settings' })).toBeVisible();
|
||||
await page.getByText('Settings', { exact: true }).click();
|
||||
await expect(page.getByRole('link', { name: 'Admin Settings' })).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user