routes erweitert

This commit is contained in:
Husky
2025-09-08 19:45:21 +02:00
parent 0d6e575fa0
commit 185f72f515
22 changed files with 455 additions and 7 deletions

8
electron/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

57
electron/.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,57 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

12
electron/.idea/electron.iml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

8
electron/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/electron.iml" filepath="$PROJECT_DIR$/.idea/electron.iml" />
</modules>
</component>
</project>

6
electron/.idea/prettier.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>

6
electron/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 KiB

View File

@@ -0,0 +1,42 @@
type UseMachine = {
machine_identification_unique: MachineIdentificationUnique;
name: MachineProperties["name"];
version: MachineProperties["version"];
slug: MachineProperties["slug"];
vendor: VendorProperties["name"];
icon: MachineProperties["icon"];
};
// returns only valid machines
export function useMachines(): UseMachine[] {
const { machines } = useMainNamespace();
if (machines?.data)
return (
machines.data.machines
.filter((machine) => machine.error === null)
.map((machine) => {
const machinePreset = getMachineProperties(
machine.machine_identification_unique.machine_identification,
);
const vendorPreset = getVendorProperties(
machinePreset!.machine_identification.vendor,
);
if (!machinePreset || !vendorPreset) {
return undefined;
}
return {
machine_identification_unique:
machine.machine_identification_unique,
name: machinePreset.name,
version: machinePreset.version,
slug: machinePreset.slug,
vendor: vendorPreset.name,
icon: machinePreset.icon,
};
})
.filter((machine) => machine !== undefined) || []
);
return [];
}

View File

@@ -0,0 +1,21 @@
import React from "react";
import { Button } from "./ui/button";
import { useRouter } from "@tanstack/react-router";
import { Icon } from "./Icon";
export function BackButton() {
const router = useRouter();
return (
<Button
onClick={() => {
router.history.back();
}}
className="h-full bg-neutral-100 text-black"
variant="ghost"
>
<Icon name="lu:ChevronLeft" />
Back
</Button>
);
}

View File

@@ -0,0 +1,37 @@
import React from "react";
import { Icon } from "@/components/Icon";
import { Button } from "@/components/ui/button";
import { useEffectAsync } from "@/lib/useEffectAsync";
export function FullscreenButton() {
const [isFullscreen, setIsFullscreen] = React.useState(false);
// We initalize button as fullscreen true if we are on QiTechOS
useEffectAsync(async () => {
const envInfo = await window.environment.getInfo();
if (envInfo.qitechOs) {
setIsFullscreen(true);
} else {
setIsFullscreen(false);
}
}, []);
return (
<Button
onClick={() => {
if (isFullscreen) {
window.electronWindow.fullscreen(false);
setIsFullscreen(false);
} else {
window.electronWindow.fullscreen(true);
setIsFullscreen(true);
}
}}
className="px-6 py-7"
variant="ghost"
>
<Icon name={isFullscreen ? "lu:Minimize" : "lu:Maximize"} />
{isFullscreen ? null : "Fullscreen"}
</Button>
);
}

View File

@@ -0,0 +1,14 @@
import React from "react";
type Props = {
children: React.ReactNode;
className?: string;
};
export function Page({ children, className }: Props) {
return (
<div className={`flex w-full flex-col gap-6 p-6 ${className || ""}`}>
{children}
</div>
);
}

View File

@@ -0,0 +1,15 @@
import React from "react";
type Props = {
title: string;
children?: React.ReactNode;
};
export function SectionTitle({children, title}: Props) {
return (
<div className="flex gap-4">
<h1 className="text-2xl">{title}</h1>
{children}
</div>
);
}

View File

@@ -1,8 +1,8 @@
import {Icon, IconName} from "./Icon";
import { useOnSubpath } from "@/lib/useOnSubpath";
import { OutsideCorner } from "./OutsideCorner";
import {Link} from "@tanstack/react-router";
import React from "react";
import {Link, Outlet} from "@tanstack/react-router";
import React, { Fragment } from "react";
type SideBarItemContent = {
link: string;
@@ -26,7 +26,7 @@ export function SidebarItem({
return (
<Link
to={link}
classname={`relative h-18 w-full ${isActive ? "pl-2": "px-2"}`}
className={`relative h-18 w-full ${isActive ? "pl-2": "px-2"}`}
>
<div className={`text-md relative z-10 flex h-full w-full items-center justify-center gap-2 ${isActive ? "rounded-l-lg bg-white pr-2": "rounded-lg bg-neutral-100"}`}>
{icon && <Icon name={icon} />}
@@ -37,8 +37,57 @@ export function SidebarItem({
);
}
const SidebarlessWidthContext = React.createContext<number | null>(null);
export function useSidebarlessWidth() {
const width = React.useContext(SidebarlessWidthContext);
if(width === null) {
throw new Error("useWidth must be used within a WidthProvider");
}
return width;
}
export function SidebarLayout() {
return (
<h1>Hello World</h1>
const [contentWidth, setContentWidth] = React.useState<number>(0);
const outletRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (outletRef.current) {
// Set initial width
setContentWidth(outletRef.current.offsetWidth);
// Create a ResizeObserver to track width changes
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
setContentWidth(entry.contentRect.width);
}
});
resizeObserver.observe(outletRef.current);
// Clean up observer on unmount
return () => {
resizeObserver.disconnect();
};
}
}, []);
return (
<SidebarlessWidthContext.Provider value={contentWidth}>
<div className="fixed flex h-full w-48 flex-col bg-neutral-200">
<div className="flex h-18 flex-col items-center justify-center gap-0 pt-2">
<div className="line-clamp-none text-3xl">Maxlan</div>
</div>
<div className="flex flex-col gap-2">
<Fragment key="localhost">
<SidebarItem link="localhost" title="Cool" activeLink="dada" isFirst={true} />
</Fragment>
</div>
</div>
<div className="ml-48 " ref={outletRef}>
<Outlet />
</div>
</SidebarlessWidthContext.Provider>
)
}

View File

@@ -0,0 +1,76 @@
import { Icon, IconName } from "@/components/Icon";
import { useOnSubpath } from "@/lib/useOnSubpath";
import { Link, Outlet } from "@tanstack/react-router";
import { OutsideCorner } from "@/components/OutsideCorner";
import React, {Fragment} from "react";
import { BackButton } from "@/components/BackButton";
import { FullscreenButton } from "@/components/FullscreenButton";
import { useSidebarlessWidth } from "@/components/SidebarLayout";
export type TopbarItemContent ={
link: string;
activeLink: string;
icon?: IconName;
title: string;
};
type TopbarItemProps = TopbarItemContent;
export function TopbarItem({ icon, title, link, activeLink }: TopbarItemProps) {
const isActive = useOnSubpath(activeLink);
return (
<Link className={`relative h-full ${isActive ? "" : "pb-2"}`} to={link}>
<div
className={`text-md relative z-10 flex h-full items-center justify-center gap-2 px-6 ${
isActive ? "rounded-t-lg bg-white pb-2" : "rounded-lg bg-neutral-100"
}`}
>
{icon && <Icon name={icon} />}
{title}
</div>
<OutsideCorner bottomLeft={isActive} bottomRight={isActive} />
</Link>
);
}
type TopbarProps = {
items: TopbarItemContent[];
pathname: string;
};
export function Topbar({ items, pathname }: TopbarProps) {
const sidebarlessWidth = useSidebarlessWidth();
return (
<div className="flex h-screen w-full flex-col">
<div
className="fixed top-0 flex h-18 gap-2 bg-neutral-200 pt-2 pr-2"
// 50 is below popup dialogs
style={{ zIndex: 50, width: sidebarlessWidth }}
>
<div className="flexflex-col z-10 pb-2">
<BackButton />
</div>
{items.map((item, index) => {
let link = item.link;
if (!item.link.startsWith("/")) {
link = pathname + "/" + item.link;
}
let activelink = item.activeLink;
if (!item.activeLink.startsWith("/")) {
activelink = pathname + "/" + item.activeLink;
}
return (
<Fragment key={index}>
<TopbarItem {...item} link={link} activeLink={activelink} />
</Fragment>
);
})}
<div className="flex-grow" />
<FullscreenButton />
</div>
<div className="mt-18 h-full overflow-y-auto">
<Outlet />
</div>
</div>
);
}

View File

@@ -0,0 +1,22 @@
import { useEffect } from "react";
export function useEffectAsync(
effect: () => Promise<void>,
deps: React.DependencyList,
): void {
useEffect(() => {
let isMounted = true;
const executeEffect = async () => {
if (isMounted) {
await effect();
}
};
executeEffect();
return () => {
isMounted = false;
};
}, deps);
}

View File

@@ -8,6 +8,6 @@ declare module "@tanstack/react-router" {
}
const history = createMemoryHistory({
initialEntries: ["/_sidebar/"],
initialEntries: ["/_sidebar/setup/ethercat"],
});
export const router = createRouter({ routeTree: rootTree, history: history });

View File

@@ -2,6 +2,8 @@ import { createRoute } from "@tanstack/react-router";
import { RootRoute } from "./__root";
import { SidebarLayout } from "@/components/SidebarLayout";
import React from "react";
import { SetupPage } from "@/setup/SetupPage";
import { EthercatPage } from "@/setup/EthercatPage";
// TODO: Steps to add a new route:
@@ -29,4 +31,23 @@ export const sidebarRoute = createRoute({
component:() => <SidebarLayout />,
});
export const rootTree = RootRoute.addChildren([sidebarRoute]);
export const setupRoute = createRoute({
getParentRoute: () => sidebarRoute,
path: "setup",
component: () => <SetupPage />
});
export const ethercatRoute = createRoute({
getParentRoute: () => setupRoute,
path: "ethercat",
component: () => <EthercatPage />,
})
export const rootTree = RootRoute.addChildren([
sidebarRoute.addChildren([
setupRoute.addChildren([
ethercatRoute,
])
]),
]);

View File

@@ -0,0 +1,15 @@
import { Page } from "@/components/Page";
import { SectionTitle } from "@/components/SectionTitle";
import React from "react";
export function EthercatPage() {
return (
<Page>
<SectionTitle title="Interface"></SectionTitle>
<p>
Ethernet Interface{" "}
<span>Discovering...</span>
</p>
</Page>
)
}

View File

@@ -0,0 +1,18 @@
import { Topbar } from "@/components/Topbar";
import React from "react";
export function SetupPage() {
return (
<Topbar
pathname="/_sidebar/setup"
items={[
{
link: "machines",
activeLink: "machines",
title: "Person",
icon: "lu:Factory",
},
]}
/>
);
}

View File

@@ -16,9 +16,19 @@ interface ElectronWindow {
minimize: () => Promise<void>;
maximize: () => Promise<void>;
close: () => Promise<void>;
fullscreen: (value: boolean) => Promise<void>;
}
interface EnvironmentInfo {
qitechOs: boolean;
}
interface EnvironmentContext {
getInfo: () => Promise<EnvironmentInfo>;
}
declare interface Window {
themeMode: ThemeModeContext;
electronWindow: ElectronWindow;
environment: EnvironmentContext;
}