Skip to content

Commit 9fc58ad

Browse files
committed
fixed multi tab feature
Signed-off-by: RAWx18 <rawx18.dev@gmail.com>
1 parent 72aec60 commit 9fc58ad

File tree

7 files changed

+391
-207
lines changed

7 files changed

+391
-207
lines changed

frontend/src/modules/layout/components/layout.vue

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<tab-bar />
55

66
<!-- Main Content Area with top padding to account for fixed tab bar -->
7-
<el-container class="flex-1 overflow-hidden bg-black relative pt-[45px]">
7+
<el-container class="flex-1 overflow-hidden bg-black relative pt-[56px]">
88
<!-- Mobile Overlay -->
99
<div
1010
v-if="isMobile && !collapsed"
@@ -48,23 +48,22 @@
4848
</banner>
4949
</div>
5050
<router-view v-slot="{ Component }">
51-
<keep-alive :include="cachedViews" :max="10">
52-
<component :is="Component" :key="route.fullPath" />
53-
</keep-alive>
51+
<component :is="Component" :key="route.fullPath" />
5452
</router-view>
5553
</el-main>
5654
</el-container>
5755
</el-container>
5856
</div>
5957
</template>
6058

61-
<script>
59+
<script lang="ts">
6260
import { mapActions, mapGetters } from 'vuex';
6361
import AppMenu from '@/modules/layout/components/menu.vue';
6462
import TabBar from '@/modules/layout/components/tab-bar.vue';
65-
import { useTabsStore } from '@/modules/layout/store/tabs';
66-
import { onMounted, onUnmounted } from 'vue';
67-
import { useRoute } from 'vue-router';
63+
import { onMounted, onUnmounted, watch } from 'vue';
64+
import { useTopNavStore } from '@/modules/layout/store/topNav';
65+
import { signalsMainMenu, chatMenu, devtelMenu } from '@/modules/layout/config/menu';
66+
import { useRoute, useRouter } from 'vue-router';
6867
6968
export default {
7069
name: 'AppLayout',
@@ -75,22 +74,56 @@ export default {
7574
},
7675
7776
setup() {
78-
const tabsStore = useTabsStore();
7977
const route = useRoute();
78+
const router = useRouter();
79+
const topNav = useTopNavStore();
80+
topNav.init();
81+
82+
// Initialize top selection based on current route
83+
// Robustly check by route name and resolved paths to avoid misclassification on refresh
84+
const findGroupForRoute = (r: any) => {
85+
const name = r.name;
86+
const fullPath = r.fullPath || '';
87+
88+
const matches = (menu: any[]) => {
89+
return menu.some((m: any) => {
90+
if (!m) return false;
91+
if (m.routeName && name && m.routeName === name) return true;
92+
if (m.path && fullPath && fullPath.startsWith(m.path)) return true;
93+
if (m.routeName) {
94+
try {
95+
const resolved = router.resolve({ name: m.routeName });
96+
if (resolved && resolved.href && fullPath.startsWith(resolved.href)) return true;
97+
} catch (e) {
98+
// ignore resolution errors
99+
}
100+
}
101+
return false;
102+
});
103+
};
80104
81-
// Check inactivity every minute
82-
let timer;
83-
onMounted(() => {
84-
timer = setInterval(() => {
85-
tabsStore.checkInactivity();
86-
}, 60000);
87-
});
105+
if (matches(chatMenu)) return 'chat';
106+
if (matches(devtelMenu)) return 'devtel';
107+
return 'signals';
108+
};
88109
89-
onUnmounted(() => {
90-
if (timer) clearInterval(timer);
110+
// Set initial top to match route (so sidebar and content align on refresh)
111+
onMounted(() => {
112+
const group = findGroupForRoute(route);
113+
topNav.set(group as any);
91114
});
92115
93-
return { tabsStore, route };
116+
// Persist last visited for the active top when route changes
117+
watch(
118+
() => route.fullPath,
119+
(p) => {
120+
const selected = topNav.selected as 'signals' | 'chat' | 'devtel';
121+
if (selected) topNav.setLastVisited(selected, p || '/');
122+
},
123+
{ immediate: true },
124+
);
125+
126+
return { route, topNav };
94127
},
95128
96129
data() {
@@ -109,9 +142,7 @@ export default {
109142
integrationsInProgress: 'integration/inProgress',
110143
}),
111144
112-
cachedViews() {
113-
return this.tabsStore.tabs.map((t) => t.name);
114-
},
145+
// No cached views: keep-alive removed to prevent background tab pages
115146
},
116147
117148
created() {
@@ -175,7 +206,7 @@ export default {
175206
// Global override for Element Plus drawers and dialogs
176207
// This ensures they respect the top bar height and don't overlap it
177208
.el-overlay {
178-
top: 45px !important; // Height of the tab bar
179-
height: calc(100% - 45px) !important;
209+
top: 56px !important; // Height of the tab bar
210+
height: calc(100% - 56px) !important;
180211
}
181212
</style>

frontend/src/modules/layout/components/menu.vue

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
<cr-menu-quickstart v-if="isQuickstartEnabled" :collapsed="isCollapsed" />
1515

1616
<!-- Menu items -->
17-
<cr-menu-links class="mb-2" :links="mainMenu" :collapsed="isCollapsed" link-class="text-sm" />
17+
<cr-menu-links class="mb-2" :links="currentMainMenu" :collapsed="isCollapsed" link-class="text-sm" />
1818

1919
<div class="border-t border-zinc-700 mb-4" />
2020

21-
<cr-menu-links :links="bottomMenu" :collapsed="isCollapsed" link-class="text-sm" />
21+
<cr-menu-links :links="currentBottomMenu" :collapsed="isCollapsed" link-class="text-sm" />
2222

2323
<div class="grow" />
2424
<!-- Support popover and Toggle -->
@@ -46,13 +46,19 @@
4646

4747
<script setup>
4848
import { useStore } from 'vuex';
49-
import { computed, watch } from 'vue';
49+
import { computed, watch, ref } from 'vue';
5050
5151
import { mapGetters } from '@/shared/vuex/vuex.helpers';
5252
import { useActivityTypeStore } from '@/modules/activity/store/type';
5353
import CrMenuWorkspace from '@/modules/layout/components/menu/menu-workspace.vue';
5454
import CrMenuLinks from '@/modules/layout/components/menu/menu-links.vue';
55-
import { bottomMenu, mainMenu } from '@/modules/layout/config/menu';
55+
import {
56+
signalsMainMenu,
57+
signalsBottomMenu,
58+
chatMenu,
59+
devtelMenu,
60+
} from '@/modules/layout/config/menu';
61+
import { useTopNavStore } from '@/modules/layout/store/topNav';
5662
import CrMenuSupport from '@/modules/layout/components/menu/menu-support.vue';
5763
import CrMenuQuickstart from '@/modules/layout/components/menu/menu-quickstart.vue';
5864
import { FeatureFlag } from '@/utils/featureFlag';
@@ -74,11 +80,30 @@ watch(
7480
const isCollapsed = computed(
7581
() => store.getters['layout/menuCollapsed'],
7682
);
83+
const topNav = useTopNavStore();
84+
85+
const selectedTop = computed(() => topNav.selected);
86+
87+
const currentMainMenu = computed(() => {
88+
if (selectedTop.value === 'chat') return chatMenu;
89+
if (selectedTop.value === 'devtel') return devtelMenu;
90+
return signalsMainMenu;
91+
});
92+
93+
const currentBottomMenu = computed(() => {
94+
if (selectedTop.value === 'chat') return [];
95+
if (selectedTop.value === 'devtel') return [];
96+
return signalsBottomMenu;
97+
});
7798
function toggleMenu() {
7899
store.dispatch('layout/toggleMenu');
79100
}
80101
81102
const isQuickstartEnabled = computed(() => FeatureFlag.isFlagEnabled(FeatureFlag.flags.quickstartV2));
103+
104+
function setTop(tab) {
105+
topNav.set(tab);
106+
}
82107
</script>
83108
84109
<script>
@@ -90,7 +115,48 @@ export default {
90115
<style lang="scss">
91116
.gitmesh-menu{
92117
.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) {
93-
width: 281px;
118+
width: 260px;
119+
background: linear-gradient(180deg, rgba(0,0,0,0.6), rgba(0,0,0,0.55));
120+
border-right: 1px solid rgba(255,255,255,0.03);
121+
backdrop-filter: blur(6px);
122+
}
123+
124+
.cr-menu-workspace, .cr-menu-quickstart {
125+
padding: 8px 12px;
126+
margin-bottom: 6px;
127+
}
128+
129+
.menu-link {
130+
border-left: 3px solid transparent;
131+
align-items: center;
132+
transition: background 150ms ease, color 150ms ease;
133+
color: rgba(255,255,255,0.86);
134+
}
135+
136+
.menu-link i {
137+
color: rgba(255,255,255,0.66);
138+
transition: color 150ms ease, transform 150ms ease;
139+
}
140+
141+
.menu-link .menu-label{
142+
color: rgba(255,255,255,0.88);
143+
font-weight: 500;
144+
letter-spacing: 0.2px;
145+
}
146+
147+
.menu-link:hover {
148+
background: rgba(255,255,255,0.02) !important;
149+
}
150+
151+
.active-menu-link{
152+
background: rgba(255,255,255,0.06) !important; /* slightly greyish */
153+
color: rgba(255,255,255,0.92) !important;
154+
border-left: none !important;
155+
font-weight: 600 !important;
156+
}
157+
158+
.active-menu-link i {
159+
color: rgba(255,255,255,0.94) !important;
94160
}
95161
}
96162
.no-scrollbar::-webkit-scrollbar {

frontend/src/modules/layout/components/menu/menu-links.vue

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,30 @@
2424
user: currentUser,
2525
tenant: currentTenant,
2626
})"
27-
class="rounded-md h-8 transition !text-zinc-400 flex items-center whitespace-nowrap
28-
flex-nowrap px-1.5 hover:bg-zinc-800 hover:!text-zinc-100 mb-2 overflow-hidden"
29-
:active-class="!disableActiveClass ? '!bg-zinc-800 font-medium !text-white' : ''"
30-
:class="[props.linkClass, props.collapsed ? 'justify-center' : '']"
27+
class="menu-link h-10 transition !text-zinc-300 flex items-center whitespace-nowrap
28+
flex-nowrap px-3 hover:bg-zinc-800 mb-2 overflow-hidden"
29+
active-class="active-menu-link"
30+
:class="[props.linkClass, props.collapsed ? 'justify-center' : '']"
3131
>
32-
<i v-if="link.icon" :class="[link.icon, props.iconClass, props.collapsed ? 'mr-0' : 'mr-3']" class="text-lg" />
33-
<span v-if="!props.collapsed" class="">
34-
{{ link.label }}
35-
</span>
32+
<i v-if="link.icon" :class="[link.icon, props.iconClass, props.collapsed ? 'mr-0' : 'mr-3']" class="text-lg" />
33+
<span v-if="!props.collapsed" class="menu-label">
34+
{{ link.label }}
35+
</span>
3636
</router-link>
3737
<a
3838
v-else-if="link.href || link.click"
3939
:id="`menu-${link.id}`"
4040
:href="link.href"
4141
target="_blank"
4242
rel="noopener noreferrer"
43-
class="rounded-md h-8 transition !text-zinc-400 flex items-center justify-between
44-
group whitespace-nowrap flex-nowrap px-1.5 hover:bg-zinc-800 hover:!text-zinc-100 mb-2 cursor-pointer overflow-hidden"
43+
class="menu-link h-10 transition !text-zinc-300 flex items-center justify-between
44+
group whitespace-nowrap flex-nowrap px-3 hover:bg-zinc-800 mb-2 cursor-pointer overflow-hidden"
4545
:class="[props.linkClass, props.collapsed ? 'justify-center' : '']"
4646
@click="link.click && link.click()"
4747
>
4848
<div class="flex items-center" :class="{ 'justify-center w-full': props.collapsed }">
4949
<i v-if="link.icon" :class="[link.icon, props.iconClass, props.collapsed ? 'mr-0' : 'mr-3']" class="text-lg" />
50-
<span v-if="!props.collapsed" class="">
50+
<span v-if="!props.collapsed" class="menu-label">
5151
{{ link.label }}
5252
</span>
5353
</div>

0 commit comments

Comments
 (0)