Skip to content

Commit 86a27c3

Browse files
committed
feat: Enhance navigation links with AppNavLink component and add disabled state styling
1 parent 9c79947 commit 86a27c3

7 files changed

Lines changed: 54 additions & 106 deletions

File tree

src/features/DebugConsole/DebugFilters.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const DebugFilters = () => {
3333
<div className="col-12 d-none d-lg-block">
3434
<DeviceFilterDropdown items={items} />
3535
<Button
36-
variant="outline"
36+
variant="outline-secondary"
3737
className="py-1 ms-1"
3838
onClick={() => dispatch(debugConsoleActions.clearAllFilters())}
3939
>

src/features/InitializationExceptions.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { skipToken } from "@reduxjs/toolkit/query";
2-
import { useState } from "react";
2+
import { Fragment, useState } from "react";
33
import useAppParams from "../shared/hooks/useAppParams";
44
import {
55
EssentialsException,
@@ -12,8 +12,6 @@ const InitializationExceptions = () => {
1212
appId ? { appId } : skipToken,
1313
);
1414

15-
console.log("Initialization exceptions:", data?.Exceptions);
16-
1715
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
1816

1917
if (isLoading) return <div className="p-3">Loading…</div>;
@@ -48,8 +46,8 @@ const InitializationExceptions = () => {
4846
{data.Exceptions.map((ex: EssentialsException, idx: number) => {
4947
const isExpanded = expandedIndex === idx;
5048
return (
51-
<>
52-
<tr key={`ex-${idx}`}>
49+
<Fragment key={`ex-${idx}`}>
50+
<tr>
5351
<td className="text-muted">{idx + 1}</td>
5452
<td>
5553
<span className="text-danger fw-semibold">
@@ -70,7 +68,7 @@ const InitializationExceptions = () => {
7068
</td>
7169
</tr>
7270
{isExpanded && ex.StackTrace && (
73-
<tr key={`ex-${idx}-trace`}>
71+
<tr>
7472
<td colSpan={3} className="p-0">
7573
<pre
7674
className="m-0 p-3 bg-light text-muted"
@@ -81,7 +79,7 @@ const InitializationExceptions = () => {
8179
</td>
8280
</tr>
8381
)}
84-
</>
82+
</Fragment>
8583
);
8684
})}
8785
</tbody>

src/features/LoginForm.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Alert, Button, Form, Spinner } from 'react-bootstrap';
33
import { Navigate, useLocation, useNavigate } from 'react-router-dom';
44
import useAppParams from '../shared/hooks/useAppParams';
55
import { useSetLoginCredentialsMutation } from '../store/apiSlice';
6-
import { selectIsAuthenticated } from '../store/auth/authSelectors';
6+
import { selectAvailableApps, selectIsAuthenticated } from '../store/auth/authSelectors';
77
import { authActions } from '../store/auth/authSlice';
88
import { useAppDispatch, useAppSelector } from '../store/hooks';
99

@@ -15,6 +15,7 @@ const ALL_APP_IDS = [
1515
const LoginForm = () => {
1616
const { appId } = useAppParams();
1717
const isAuthenticated = useAppSelector(selectIsAuthenticated);
18+
const availableApps = useAppSelector(selectAvailableApps);
1819
const dispatch = useAppDispatch();
1920
const navigate = useNavigate();
2021
const location = useLocation();
@@ -30,9 +31,10 @@ const LoginForm = () => {
3031

3132
const isValidAppId = appId && ALL_APP_IDS.includes(appId);
3233
const probeAppId = isValidAppId ? appId : ALL_APP_IDS[0];
34+
const safeAppId = isValidAppId ? appId : (availableApps[0] ?? probeAppId);
3335

3436
if (isAuthenticated) {
35-
return <Navigate to={from ?? `/${appId}/versions`} replace />;
37+
return <Navigate to={from ?? `/${safeAppId}/versions`} replace />;
3638
}
3739

3840
async function handleSubmit(e: FormEvent) {

src/features/MobileControl.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616

1717
const MobileControl = () => {
1818
const { appId } = useAppParams();
19-
console.log("AppId in MobileControl", appId);
2019

2120
const { data: info } = useGetMobileControlInfoQuery(
2221
appId ? { appId, deviceKey: "appServer" } : skipToken,

src/features/Routing.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,20 @@ function buildGraph(
125125
: d,
126126
);
127127

128+
const effectiveDeviceKeys = new Set(effectiveDevices.map((d) => d.key));
128129
// Collect one unique device-pair edge per source→destination (dagre only
129-
// needs connectivity, not multiplicity, for rank assignment).
130+
// needs connectivity, not multiplicity, for rank assignment). Only use
131+
// visible tie lines whose endpoints are present in the visible device set
132+
// so hidden devices cannot be implicitly added to the layout graph.
130133
const uniquePairs = [
131134
...new Set(
132-
data.tieLines.map(
133-
(tl) => `${tl.sourceDeviceKey}|${tl.destinationDeviceKey}`,
134-
),
135+
visibleTieLines
136+
.filter(
137+
(tl) =>
138+
effectiveDeviceKeys.has(tl.sourceDeviceKey) &&
139+
effectiveDeviceKeys.has(tl.destinationDeviceKey),
140+
)
141+
.map((tl) => `${tl.sourceDeviceKey}|${tl.destinationDeviceKey}`),
135142
),
136143
];
137144

src/features/TopNav.tsx

Lines changed: 29 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@ import { useGetVersionsQuery } from "../store/apiSlice";
88
import { selectAvailableApps } from "../store/auth/authSelectors";
99
import { useAppSelector } from "../store/hooks";
1010

11+
const AppNavLink = ({
12+
appId,
13+
path,
14+
children,
15+
}: {
16+
appId: string | undefined;
17+
path: string;
18+
children: React.ReactNode;
19+
}) => {
20+
if (!appId) {
21+
return <span className="me-3 text-muted nav-link-disabled">{children}</span>;
22+
}
23+
return (
24+
<NavLink className="me-3" to={`/${appId}/${path}`}>
25+
{children}
26+
</NavLink>
27+
);
28+
};
29+
1130
const TopNav = ({ isConnected }: { isConnected: boolean }) => {
1231
const location = useLocation();
1332
const params = useAppParams();
@@ -71,100 +90,19 @@ const TopNav = ({ isConnected }: { isConnected: boolean }) => {
7190
</Dropdown>
7291
)}
7392
<Nav className="me-auto">
74-
<NavLink
75-
className={
76-
location.pathname.includes(`/${params.appId}/versions`)
77-
? "text-secondary me-3"
78-
: "me-3"
79-
}
80-
to={`/${params.appId}/versions`}
81-
>
82-
Versions
83-
</NavLink>
84-
<NavLink
85-
className={
86-
location.pathname.includes(`/${params.appId}/apiPaths`)
87-
? "text-secondary me-3"
88-
: "me-3"
89-
}
90-
to={`/${params.appId}/apiPaths`}
91-
>
92-
API Paths
93-
</NavLink>
93+
<AppNavLink appId={params.appId} path="versions">Versions</AppNavLink>
94+
<AppNavLink appId={params.appId} path="apiPaths">API Paths</AppNavLink>
9495
{showInitializationExceptions && (
95-
<NavLink
96-
className={
97-
location.pathname.includes(
98-
`/${params.appId}/initializationExceptions`,
99-
)
100-
? "text-secondary me-3"
101-
: "me-3"
102-
}
103-
to={`/${params.appId}/initializationExceptions`}
104-
>
96+
<AppNavLink appId={params.appId} path="initializationExceptions">
10597
Initialization Exceptions
106-
</NavLink>
98+
</AppNavLink>
10799
)}
108-
<NavLink
109-
className={
110-
location.pathname.includes(`/${params.appId}/console`)
111-
? "text-secondary me-3"
112-
: "me-3"
113-
}
114-
to={`/${params.appId}/console`}
115-
>
116-
Debug Console
117-
</NavLink>
118-
<NavLink
119-
className={
120-
location.pathname.includes(`/${params.appId}/config`)
121-
? "text-secondary me-3"
122-
: "me-3"
123-
}
124-
to={`/${params.appId}/config`}
125-
>
126-
Config File
127-
</NavLink>
128-
<NavLink
129-
className={
130-
location.pathname.includes(`/${params.appId}/devices`)
131-
? "text-secondary me-3"
132-
: "me-3"
133-
}
134-
to={`/${params.appId}/devices`}
135-
>
136-
Devices
137-
</NavLink>
138-
<NavLink
139-
className={
140-
location.pathname.includes(`/${params.appId}/types`)
141-
? "text-secondary me-3"
142-
: "me-3"
143-
}
144-
to={`/${params.appId}/types`}
145-
>
146-
Types
147-
</NavLink>
148-
<NavLink
149-
className={
150-
location.pathname.includes(`/${params.appId}/routing`)
151-
? "text-secondary me-3"
152-
: "me-3"
153-
}
154-
to={`/${params.appId}/routing`}
155-
>
156-
Routing
157-
</NavLink>
158-
<NavLink
159-
className={
160-
location.pathname.includes(`/${params.appId}/mobileControl`)
161-
? "text-secondary me-3"
162-
: "me-3"
163-
}
164-
to={`/${params.appId}/mobileControl`}
165-
>
166-
Mobile Control
167-
</NavLink>
100+
<AppNavLink appId={params.appId} path="console">Debug Console</AppNavLink>
101+
<AppNavLink appId={params.appId} path="config">Config File</AppNavLink>
102+
<AppNavLink appId={params.appId} path="devices">Devices</AppNavLink>
103+
<AppNavLink appId={params.appId} path="types">Types</AppNavLink>
104+
<AppNavLink appId={params.appId} path="routing">Routing</AppNavLink>
105+
<AppNavLink appId={params.appId} path="mobileControl">Mobile Control</AppNavLink>
168106
</Nav>
169107
<div className="d-flex align-items-center">
170108
<IconDarkEllipse

src/styles.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,3 +894,7 @@ textarea:hover {
894894
// max-height: 25rem;
895895
// overflow: hidden ;
896896
// }
897+
898+
.nav-link-disabled {
899+
cursor: default;
900+
}

0 commit comments

Comments
 (0)