0
0
Fork 0
salix-front-mindshore-fork2/src/components/LeftMenu.vue

344 lines
11 KiB
Vue

<script setup>
import axios from 'axios';
import { onMounted, watch, ref, reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { QSeparator, useQuasar } from 'quasar';
import { useRoute } from 'vue-router';
import { useNavigationStore } from 'src/stores/useNavigationStore';
import { toLowerCamel } from 'src/filters';
import routes from 'src/router/modules';
import LeftMenuItem from './LeftMenuItem.vue';
import LeftMenuItemGroup from './LeftMenuItemGroup.vue';
import VnInput from './common/VnInput.vue';
const { t } = useI18n();
const route = useRoute();
const quasar = useQuasar();
const navigation = useNavigationStore();
const props = defineProps({
source: {
type: String,
default: 'main',
},
});
const initialized = ref(false);
const items = ref([]);
const expansionItemElements = reactive({});
const pinnedModules = computed(() => {
const map = new Map();
items.value.forEach((item) => item.isPinned && map.set(item.name, item));
return map;
});
const search = ref(null);
const filteredItems = computed(() => {
if (!search.value) return items.value;
const normalizedSearch = normalize(search.value);
return items.value.filter((item) => {
const locale = normalize(t(item.title));
return locale.includes(normalizedSearch);
});
});
const filteredPinnedModules = computed(() => {
if (!search.value) return pinnedModules.value;
const normalizedSearch = search.value
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
const map = new Map();
for (const [key, pinnedModule] of pinnedModules.value) {
const locale = t(pinnedModule.title)
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
if (locale.includes(normalizedSearch)) map.set(key, pinnedModule);
}
return map;
});
onMounted(async () => {
await navigation.fetchPinned();
getRoutes();
initialized.value = true;
});
watch(
() => route.matched,
() => {
if (!initialized.value) return;
items.value = [];
getRoutes();
},
{ deep: true }
);
function findMatches(search, item) {
const matches = [];
function findRoute(search, item) {
for (const child of item.children) {
if (search?.indexOf(child.name) > -1) {
matches.push(child);
} else if (child.children) {
findRoute(search, child);
}
}
}
findRoute(search, item);
return matches;
}
function addChildren(module, route, parent) {
const menus = route?.meta?.menu ?? route?.menus?.[props.source]; //backwards compatible
if (!menus) return;
const matches = findMatches(menus, route);
for (const child of matches) {
navigation.addMenuItem(module, child, parent);
}
}
function getRoutes() {
if (props.source === 'main') {
const modules = Object.assign([], navigation.getModules().value);
for (const item of modules) {
const moduleDef = routes.find(
(route) => toLowerCamel(route.name) === item.module
);
if (!moduleDef) continue;
item.children = [];
addChildren(item.module, moduleDef, item.children);
}
items.value = modules;
}
if (props.source === 'card') {
const currentRoute = route.matched[1];
const currentModule = toLowerCamel(currentRoute.name);
let moduleDef = routes.find(
(route) => toLowerCamel(route.name) === currentModule
);
if (!moduleDef) return;
if (!moduleDef?.menus) moduleDef = betaGetRoutes();
addChildren(currentModule, moduleDef, items.value);
}
}
function betaGetRoutes() {
let menuRoute;
let index = route.matched.length - 1;
while (!menuRoute && index > 0) {
if (route.matched[index]?.meta?.menu) menuRoute = route.matched[index];
index--;
}
return menuRoute;
}
async function togglePinned(item, event) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
const data = { moduleName: item.module };
const response = await axios.post('StarredModules/toggleStarredModule', data);
item.isPinned = false;
if (response.data && response.data.id) {
item.isPinned = true;
}
navigation.togglePinned(item.module);
quasar.notify({
message: t('globals.dataSaved'),
type: 'positive',
});
}
const handleItemExpansion = (itemName) => {
expansionItemElements[itemName].scrollToLastElement();
};
function normalize(text) {
return text
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
}
</script>
<template>
<QList padding class="column-max-width">
<template v-if="$props.source === 'main'">
<template v-if="$route?.matched[1]?.name === 'Dashboard'">
<QItem class="q-pb-md">
<VnInput
v-model="search"
:label="t('Search modules')"
class="full-width"
filled
dense
autofocus
/>
</QItem>
<QSeparator />
<template v-if="filteredPinnedModules.size">
<LeftMenuItem
v-for="[key, pinnedModule] of filteredPinnedModules"
:key="key"
:item="pinnedModule"
group="modules"
>
<template #side>
<QBtn
v-if="pinnedModule.isPinned === true"
@click="togglePinned(pinnedModule, $event)"
icon="remove_circle"
size="xs"
flat
round
>
<QTooltip>
{{ t('components.leftMenu.removeFromPinned') }}
</QTooltip>
</QBtn>
</template>
</LeftMenuItem>
<QSeparator />
</template>
<template v-for="item in filteredItems" :key="item.name">
<template
v-if="item.children && !filteredPinnedModules.has(item.name)"
>
<LeftMenuItem :item="item" group="modules">
<template #side>
<QBtn
v-if="item.isPinned === true"
@click="togglePinned(item, $event)"
icon="remove_circle"
size="xs"
flat
round
>
<QTooltip>
{{ t('components.leftMenu.removeFromPinned') }}
</QTooltip>
</QBtn>
<QBtn
v-if="item.isPinned === false"
@click="togglePinned(item, $event)"
icon="push_pin"
size="xs"
flat
round
>
<QTooltip>
{{ t('components.leftMenu.addToPinned') }}
</QTooltip>
</QBtn>
</template>
</LeftMenuItem>
</template>
</template>
</template>
<template v-for="item in items" :key="item.name">
<template v-if="item.name === $route?.matched[1]?.name">
<QItem class="header">
<QItemSection avatar v-if="item.icon">
<QIcon :name="item.icon" />
</QItemSection>
<QItemSection avatar v-if="!item.icon">
<QIcon name="disabled_by_default" />
</QItemSection>
<QItemSection>{{ t(item.title) }}</QItemSection>
<QItemSection side>
<slot name="side" :item="item" />
</QItemSection>
</QItem>
<QSeparator />
<template v-if="item.children">
<LeftMenuItemGroup :item="item" group="modules">
<template #side>
<QBtn
v-if="item.isPinned === true"
@click="togglePinned(item, $event)"
icon="remove_circle"
size="xs"
flat
round
>
<QTooltip>
{{ t('components.leftMenu.removeFromPinned') }}
</QTooltip>
</QBtn>
<QBtn
v-if="item.isPinned === false"
@click="togglePinned(item, $event)"
icon="push_pin"
size="xs"
flat
round
>
<QTooltip>
{{ t('components.leftMenu.addToPinned') }}
</QTooltip>
</QBtn>
</template>
</LeftMenuItemGroup>
</template>
</template>
</template>
</template>
<template v-if="$props.source === 'card'">
<template v-for="item in items" :key="item.name">
<LeftMenuItem v-if="!item.children" :item="item" />
<QList v-else>
<QExpansionItem
v-ripple
clickable
:icon="item.icon"
:label="t(item.title)"
:content-inset-level="0.5"
@after-show="handleItemExpansion(item.name)"
>
<LeftMenuItemGroup
:ref="(el) => (expansionItemElements[item.name] = el)"
:item="item"
/>
</QExpansionItem>
</QList>
</template>
</template>
</QList>
</template>
<style scoped>
.pinned .q-btn {
visibility: hidden;
}
.pinned:hover .q-btn {
visibility: visible;
}
.column-max-width {
max-width: 256px;
}
.header {
color: var(--vn-label-color);
}
</style>
<i18n>
es:
Search modules: Buscar módulos
</i18n>