173 lines
4.8 KiB
TypeScript
173 lines
4.8 KiB
TypeScript
import {
|
|
createRouter,
|
|
createWebHashHistory,
|
|
type RouteLocationNormalizedGeneric,
|
|
} from "vue-router";
|
|
import Modal from "bootstrap/js/dist/modal";
|
|
import { ensureCurrentLocaleMessages } from "./i18n.ts";
|
|
import {
|
|
openLoginModal,
|
|
statusUnknown,
|
|
updateAuthStatus,
|
|
isLoggedIn,
|
|
isConfigured,
|
|
} from "./components/Auth/auth";
|
|
import { initConfigModal } from "./configModal";
|
|
import { hapticFeedback } from "./utils/haptic";
|
|
import type { VueI18nInstance } from "vue-i18n";
|
|
|
|
function hideAllModals() {
|
|
[...document.querySelectorAll(".modal.show")].forEach((modal) => {
|
|
// skip unclosable modals
|
|
if (modal.getAttribute("data-bs-backdrop") === "static") return;
|
|
|
|
const modalInstance = Modal.getInstance(modal);
|
|
if (modalInstance) {
|
|
modalInstance.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
async function ensureAuth(to: RouteLocationNormalizedGeneric) {
|
|
await updateAuthStatus();
|
|
if (!isConfigured()) {
|
|
return false;
|
|
}
|
|
if (!isLoggedIn() && !statusUnknown()) {
|
|
openLoginModal(to.path);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Custom stringifyQuery to keep brackets unencoded in URLs
|
|
export function stringifyQuery(query?: Record<string, any>): string {
|
|
if (!query) return "";
|
|
const parts: string[] = [];
|
|
for (const key of Object.keys(query)) {
|
|
const value = query[key];
|
|
if (Array.isArray(value)) {
|
|
for (const v of value) {
|
|
parts.push(v ? `${key}=${encodeURIComponent(v)}` : key);
|
|
}
|
|
} else if (value) {
|
|
parts.push(`${key}=${encodeURIComponent(value)}`);
|
|
} else {
|
|
parts.push(key);
|
|
}
|
|
}
|
|
return parts.join("&");
|
|
}
|
|
|
|
export default function setupRouter(i18n: VueI18nInstance) {
|
|
const router = createRouter({
|
|
history: createWebHashHistory(),
|
|
stringifyQuery,
|
|
scrollBehavior(to, from) {
|
|
if (to.hash) {
|
|
return new Promise((resolve) => {
|
|
const check = () => {
|
|
if (document.querySelector(to.hash)) {
|
|
setTimeout(() => resolve({ el: to.hash, behavior: "smooth" }), 200);
|
|
} else {
|
|
requestAnimationFrame(check);
|
|
}
|
|
};
|
|
check();
|
|
});
|
|
}
|
|
return to.path !== from.path ? { top: 0, behavior: "instant" } : false;
|
|
},
|
|
routes: [
|
|
{
|
|
path: "/",
|
|
component: () => import("./views/Main.vue"),
|
|
props: (route) => {
|
|
const { lp } = route.query;
|
|
return {
|
|
selectedLoadpointId: lp as string | undefined,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
path: "/config",
|
|
component: () => import("./views/Config.vue"),
|
|
beforeEnter: ensureAuth,
|
|
props: true,
|
|
},
|
|
{
|
|
path: "/sessions",
|
|
component: () => import("./views/Sessions.vue"),
|
|
props: (route) => {
|
|
const { month, year, loadpoint, vehicle, period } = route.query;
|
|
return {
|
|
month: month ? parseInt(month as string, 10) : undefined,
|
|
year: year ? parseInt(year as string, 10) : undefined,
|
|
period: period ?? undefined,
|
|
loadpointFilter: loadpoint ?? undefined,
|
|
vehicleFilter: vehicle ?? undefined,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
path: "/forecast",
|
|
component: () => import("./views/Forecast.vue"),
|
|
props: true,
|
|
},
|
|
{
|
|
path: "/battery",
|
|
component: () => import("./views/Battery.vue"),
|
|
props: true,
|
|
},
|
|
{
|
|
path: "/history",
|
|
component: () => import("./views/History.vue"),
|
|
props: (route) => {
|
|
const { day, month, year, period } = route.query;
|
|
return {
|
|
day: day ? parseInt(day as string, 10) : undefined,
|
|
month: month ? parseInt(month as string, 10) : undefined,
|
|
year: year ? parseInt(year as string, 10) : undefined,
|
|
period: period ?? undefined,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
path: "/optimize",
|
|
component: () => import("./views/Optimize.vue"),
|
|
props: true,
|
|
},
|
|
{
|
|
path: "/log",
|
|
component: () => import("./views/Log.vue"),
|
|
beforeEnter: ensureAuth,
|
|
props: (route) => {
|
|
const { areas, level } = route.query;
|
|
return {
|
|
areas: typeof areas === "string" ? areas.split(",") : undefined,
|
|
level: typeof level === "string" ? level : undefined,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
path: "/issue",
|
|
component: () => import("./views/Issue.vue"),
|
|
beforeEnter: ensureAuth,
|
|
props: true,
|
|
},
|
|
],
|
|
});
|
|
router.beforeEach(async () => {
|
|
await ensureCurrentLocaleMessages(i18n);
|
|
return true;
|
|
});
|
|
router.afterEach((to, from) => {
|
|
// Only hide modals when the actual route path changes, not query parameters
|
|
if (to.path !== from.path) {
|
|
hideAllModals();
|
|
hapticFeedback();
|
|
}
|
|
});
|
|
initConfigModal(router);
|
|
return router;
|
|
}
|