Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Website/components/datamodelview/Attributes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useState } from "react"
import { ArrowUpDown, ArrowUp, ArrowDown, EyeOff, Eye, Search, X } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./../ui/select"
import { Input } from "../ui/input"
import { AttributeDetails } from "./../entity/AttributeDetails"
import { AttributeDetails } from "./entity/AttributeDetails"
import BooleanAttribute from "./../attributes/BooleanAttribute"
import ChoiceAttribute from "./../attributes/ChoiceAttribute"
import DateTimeAttribute from "./../attributes/DateTimeAttribute"
Expand Down
5 changes: 5 additions & 0 deletions Website/components/datamodelview/DatamodelView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { List } from "./List";
import { TimeSlicedSearch } from "./TimeSlicedSearch";
import React, { useState, useEffect, useRef, useCallback } from "react";
import { useDatamodelData, useDatamodelDataDispatch } from "@/contexts/DatamodelDataContext";
import { updateURL } from "@/lib/url-utils";
import { useSearchParams } from "next/navigation";

export function DatamodelView() {
const dispatch = useSidebarDispatch();
Expand Down Expand Up @@ -38,6 +40,7 @@ function DatamodelViewContent() {

// Calculate total search results
const totalResults = filtered.length > 0 ? filtered.filter(item => item.type === 'entity').length : 0;
const initialLocalValue = useSearchParams().get('globalsearch') || "";

// Isolated search handlers - these don't depend on component state
const handleSearch = useCallback((searchValue: string) => {
Expand All @@ -49,6 +52,7 @@ function DatamodelViewContent() {
datamodelDataDispatch({ type: "SET_FILTERED", payload: [] });
}
}
updateURL({ query: { globalsearch: searchValue.length >= 3 ? searchValue : "" } })
datamodelDataDispatch({ type: "SET_SEARCH", payload: searchValue.length >= 3 ? searchValue : "" });
setCurrentSearchIndex(searchValue.length >= 3 ? 1 : 0); // Reset to first result when searching, 0 when cleared
}, [groups, datamodelDataDispatch]);
Expand Down Expand Up @@ -207,6 +211,7 @@ function DatamodelViewContent() {
onLoadingChange={handleLoadingChange}
onNavigateNext={handleNavigateNext}
onNavigatePrevious={handleNavigatePrevious}
initialLocalValue={initialLocalValue}
currentIndex={currentSearchIndex}
totalResults={totalResults}
/>
Expand Down
5 changes: 3 additions & 2 deletions Website/components/datamodelview/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useVirtualizer } from '@tanstack/react-virtual';
import { Section } from "./Section";
import { useDatamodelData } from "@/contexts/DatamodelDataContext";
import { AttributeType, EntityType, GroupType } from "@/lib/Types";
import { updateURL } from "@/lib/url-utils";

interface IListProps {
}
Expand Down Expand Up @@ -84,7 +85,7 @@ export const List = ({ }: IListProps) => {
return item.type === 'group' ? 92 : 300;
},
// Override scroll behavior to prevent jumping during tab switches
scrollToFn: (offset, options) => {
scrollToFn: (offset) => {
// When switching tabs during search, don't change scroll position
if (isTabSwitching.current && !isIntentionalScroll.current) {
return;
Expand All @@ -98,7 +99,6 @@ export const List = ({ }: IListProps) => {
// Default scroll behavior for other cases
const scrollElement = parentRef.current;
if (scrollElement) {
console.log("setting scroll to: " + offset + " - " + options.adjustments)
scrollElement.scrollTop = offset;
}
},
Expand Down Expand Up @@ -235,6 +235,7 @@ export const List = ({ }: IListProps) => {
const item = flatItems[firstVisibleItem.index];
if (item?.type === 'entity') {
if (item.entity.SchemaName !== datamodelView.currentSection) {
updateURL({ query: { group: item.group.Name, section: item.entity.SchemaName } });
dispatch({ type: "SET_CURRENT_GROUP", payload: item.group.Name });
dispatch({ type: "SET_CURRENT_SECTION", payload: item.entity.SchemaName });
}
Expand Down
2 changes: 1 addition & 1 deletion Website/components/datamodelview/Relationships.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { EntityType } from "@/lib/Types"
import { TableHeader, TableRow, TableHead, TableBody, TableCell, Table } from "../ui/table"
import { Button } from "../ui/button"
import { CascadeConfiguration } from "../entity/CascadeConfiguration"
import { CascadeConfiguration } from "./entity/CascadeConfiguration"
import { useState } from "react"
import { ArrowUpDown, ArrowUp, ArrowDown, Search, X } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
Expand Down
6 changes: 3 additions & 3 deletions Website/components/datamodelview/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import { EntityType, GroupType } from "@/lib/Types"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../ui/tabs"
import { EntityHeader } from "../entity/EntityHeader"
import { SecurityRoles } from "../entity/SecurityRoles"
import { EntityHeader } from "./entity/EntityHeader"
import { SecurityRoles } from "./entity/SecurityRoles"
import Keys from "./Keys"
import { KeyRound, Tags, Unplug } from "lucide-react"
import { Attributes } from "./Attributes"
Expand All @@ -28,7 +28,7 @@ export const Section = React.memo(
// Handle tab changes to notify parent component
const handleTabChange = React.useCallback((value: string) => {
if (onTabChange) {
onTabChange(true); // Signal that tab switching is starting
onTabChange(true);
}
setTab(value);
}, [onTabChange]);
Expand Down
4 changes: 3 additions & 1 deletion Website/components/datamodelview/TimeSlicedSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface TimeSlicedSearchProps {
onLoadingChange: (loading: boolean) => void;
onNavigateNext?: () => void;
onNavigatePrevious?: () => void;
initialLocalValue: string;
currentIndex?: number;
totalResults?: number;
placeholder?: string;
Expand All @@ -23,11 +24,12 @@ export const TimeSlicedSearch = ({
onLoadingChange,
onNavigateNext,
onNavigatePrevious,
initialLocalValue,
currentIndex = 0,
totalResults = 0,
placeholder = "Search attributes...",
}: TimeSlicedSearchProps) => {
const [localValue, setLocalValue] = useState('');
const [localValue, setLocalValue] = useState(initialLocalValue);
const [isTyping, setIsTyping] = useState(false);
const [portalRoot, setPortalRoot] = useState<HTMLElement | null>(null);
const [lastValidSearch, setLastValidSearch] = useState('');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { AttributeType, CalculationMethods, RequiredLevel } from "@/lib/Types";
import { Calculator, CircleAlert, CirclePlus, Eye, Lock, Sigma, Zap } from "lucide-react";
import { HybridTooltip, HybridTooltipContent, HybridTooltipTrigger } from "../ui/hybridtooltop";
import { HybridTooltip, HybridTooltipContent, HybridTooltipTrigger } from "../../ui/hybridtooltop";

export function AttributeDetails({ attribute }: { attribute: AttributeType }) {
const details = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { EntityType, OwnershipType } from "@/lib/Types";
import { Eye, ClipboardList, Paperclip, Building, Users } from "lucide-react";
import { HybridTooltip, HybridTooltipContent, HybridTooltipTrigger } from "../ui/hybridtooltop";
import { HybridTooltip, HybridTooltipContent, HybridTooltipTrigger } from "../../ui/hybridtooltop";

type EntityDetailType = {
icon: JSX.Element;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { SecurityRole, PrivilegeDepth } from "@/lib/Types";
import { Ban, User, Users, Boxes, Building2, Minus } from "lucide-react";
import { HybridTooltip, HybridTooltipContent, HybridTooltipTrigger } from "../ui/hybridtooltop";
import { HybridTooltip, HybridTooltipContent, HybridTooltipTrigger } from "../../ui/hybridtooltop";

export function SecurityRoles({ roles }: { roles: SecurityRole[] }) {
return (
Expand Down
7 changes: 7 additions & 0 deletions Website/contexts/DatamodelDataContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React, { createContext, useContext, useReducer, ReactNode } from "react";
import { GroupType } from "@/lib/Types";
import { useSearchParams } from "next/navigation";

interface DatamodelDataState {
groups: GroupType[];
Expand Down Expand Up @@ -36,7 +37,13 @@ const datamodelDataReducer = (state: DatamodelDataState, action: any): Datamodel
export const DatamodelDataProvider = ({ children }: { children: ReactNode }) => {
const [state, dispatch] = useReducer(datamodelDataReducer, initialState);

const searchParams = useSearchParams();
const globalsearchParam = searchParams.get('globalsearch');

React.useEffect(() => {

dispatch({ type: "SET_SEARCH", payload: globalsearchParam || "" });

const worker = new Worker(new URL("../components/datamodelview/dataLoaderWorker.js", import.meta.url));
worker.onmessage = (e) => {
dispatch({ type: "SET_GROUPS", payload: e.data });
Expand Down
12 changes: 9 additions & 3 deletions Website/contexts/DatamodelViewContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,17 @@ export const DatamodelViewProvider = ({ children }: { children: ReactNode }) =>
const [datamodelViewState, dispatch] = useReducer(datamodelViewReducer, initialState);

const searchParams = useSearchParams();
const entityParam = searchParams.get('section');
const sectionParam = searchParams.get('section');
const groupParam = searchParams.get('group');

// on initial load set data from query params
useEffect(() => {
dispatch({ type: "SET_CURRENT_GROUP", payload: entityParam });
}, [entityParam])
if (!sectionParam) return;
try { datamodelViewState.scrollToSection(""); } catch { return; }
dispatch({ type: "SET_CURRENT_GROUP", payload: groupParam });
dispatch({ type: "SET_CURRENT_SECTION", payload: sectionParam });
datamodelViewState.scrollToSection(sectionParam);
}, [datamodelViewState.scrollToSection])

return (
<DatamodelViewContext.Provider value={{ ...datamodelViewState }}>
Expand Down
40 changes: 40 additions & 0 deletions Website/lib/url-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
type UpdateMode = "push" | "replace";

interface URLParts {
/** e.g. "/inbox/42" (must be same-origin; you can't change domain/protocol/port) */
path?: string;
/** set value to null/undefined to remove a param */
query?: Record<string, string | number | boolean | null | undefined>;
/** e.g. "section-2" or "#section-2"; pass "" to clear; omit to leave unchanged */
hash?: string;
/** optional state object you want back on popstate */
state?: unknown;
/** optional document title (mostly ignored by browsers) */
title?: string;
}

export function updateURL(
{ path, query, hash, state, title }: URLParts,
mode: UpdateMode = "push"
): void {
const url = new URL(window.location.href);

if (typeof path === "string") {
url.pathname = path.startsWith("/") ? path : `/${path}`;
}

if (query) {
for (const [k, v] of Object.entries(query)) {
if (v === null || v === undefined) url.searchParams.delete(k);
else url.searchParams.set(k, String(v));
}
}

if (hash !== undefined) {
url.hash = hash ? (hash.startsWith("#") ? hash : `#${hash}`) : "";
}

const method = mode === "push" ? "pushState" : "replaceState";
window.history[method](state ?? {}, title ?? "", url);
if (title) document.title = title; // cross-browser title update
}
Loading