diff --git a/electron/.idea/.gitignore b/electron/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/electron/.idea/.gitignore @@ -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 diff --git a/electron/.idea/codeStyles/Project.xml b/electron/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..49630cc --- /dev/null +++ b/electron/.idea/codeStyles/Project.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/electron/.idea/codeStyles/codeStyleConfig.xml b/electron/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/electron/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/electron/.idea/electron.iml b/electron/.idea/electron.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/electron/.idea/electron.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/electron/.idea/inspectionProfiles/Project_Default.xml b/electron/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/electron/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/electron/.idea/modules.xml b/electron/.idea/modules.xml new file mode 100644 index 0000000..417dada --- /dev/null +++ b/electron/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/electron/.idea/prettier.xml b/electron/.idea/prettier.xml new file mode 100644 index 0000000..b0c1c68 --- /dev/null +++ b/electron/.idea/prettier.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/electron/.idea/vcs.xml b/electron/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/electron/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/electron/images/demo.gif b/electron/images/demo.gif deleted file mode 100644 index 1856825..0000000 Binary files a/electron/images/demo.gif and /dev/null differ diff --git a/electron/src/client/useMachines.tsx b/electron/src/client/useMachines.tsx new file mode 100644 index 0000000..5c287f3 --- /dev/null +++ b/electron/src/client/useMachines.tsx @@ -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 []; +} diff --git a/electron/src/components/BackButton.tsx b/electron/src/components/BackButton.tsx new file mode 100644 index 0000000..386369e --- /dev/null +++ b/electron/src/components/BackButton.tsx @@ -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 ( + + ); +} \ No newline at end of file diff --git a/electron/src/components/FullscreenButton.tsx b/electron/src/components/FullscreenButton.tsx new file mode 100644 index 0000000..a9561f9 --- /dev/null +++ b/electron/src/components/FullscreenButton.tsx @@ -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 ( + + ); +} diff --git a/electron/src/components/Page.tsx b/electron/src/components/Page.tsx new file mode 100644 index 0000000..6b2275e --- /dev/null +++ b/electron/src/components/Page.tsx @@ -0,0 +1,14 @@ +import React from "react"; + +type Props = { + children: React.ReactNode; + className?: string; +}; + +export function Page({ children, className }: Props) { + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/electron/src/components/SectionTitle.tsx b/electron/src/components/SectionTitle.tsx new file mode 100644 index 0000000..f34a31d --- /dev/null +++ b/electron/src/components/SectionTitle.tsx @@ -0,0 +1,15 @@ +import React from "react"; + +type Props = { + title: string; + children?: React.ReactNode; +}; + +export function SectionTitle({children, title}: Props) { + return ( +
+

{title}

+ {children} +
+ ); +} \ No newline at end of file diff --git a/electron/src/components/SidebarLayout.tsx b/electron/src/components/SidebarLayout.tsx index 75a6f2a..54b1f1c 100644 --- a/electron/src/components/SidebarLayout.tsx +++ b/electron/src/components/SidebarLayout.tsx @@ -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 (
{icon && } @@ -37,8 +37,57 @@ export function SidebarItem({ ); } +const SidebarlessWidthContext = React.createContext(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 ( -

Hello World

+ const [contentWidth, setContentWidth] = React.useState(0); + + + const outletRef = React.useRef(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 ( + +
+
+
Maxlan
+
+
+ + + +
+
+
+ +
+
) } \ No newline at end of file diff --git a/electron/src/components/Topbar.tsx b/electron/src/components/Topbar.tsx index e69de29..b62912a 100644 --- a/electron/src/components/Topbar.tsx +++ b/electron/src/components/Topbar.tsx @@ -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 ( + +
+ {icon && } + {title} +
+ + + ); +} + +type TopbarProps = { + items: TopbarItemContent[]; + pathname: string; +}; + +export function Topbar({ items, pathname }: TopbarProps) { + const sidebarlessWidth = useSidebarlessWidth(); + return ( +
+
+
+ +
+ {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 ( + + + + ); + })} +
+ +
+ +
+ +
+
+ ); +} diff --git a/electron/src/lib/useEffectAsync.tsx b/electron/src/lib/useEffectAsync.tsx new file mode 100644 index 0000000..8e1539e --- /dev/null +++ b/electron/src/lib/useEffectAsync.tsx @@ -0,0 +1,22 @@ +import { useEffect } from "react"; + +export function useEffectAsync( + effect: () => Promise, + deps: React.DependencyList, +): void { + useEffect(() => { + let isMounted = true; + + const executeEffect = async () => { + if (isMounted) { + await effect(); + } + }; + + executeEffect(); + + return () => { + isMounted = false; + }; + }, deps); +} diff --git a/electron/src/routes/router.tsx b/electron/src/routes/router.tsx index 6cd073e..8ed0ee9 100644 --- a/electron/src/routes/router.tsx +++ b/electron/src/routes/router.tsx @@ -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 }); diff --git a/electron/src/routes/routes.tsx b/electron/src/routes/routes.tsx index 8cc6b4f..30062ef 100644 --- a/electron/src/routes/routes.tsx +++ b/electron/src/routes/routes.tsx @@ -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:() => , }); -export const rootTree = RootRoute.addChildren([sidebarRoute]); +export const setupRoute = createRoute({ + getParentRoute: () => sidebarRoute, + path: "setup", + component: () => +}); + +export const ethercatRoute = createRoute({ + getParentRoute: () => setupRoute, + path: "ethercat", + component: () => , +}) + +export const rootTree = RootRoute.addChildren([ + sidebarRoute.addChildren([ + setupRoute.addChildren([ + ethercatRoute, + ]) + ]), + +]); diff --git a/electron/src/setup/EthercatPage.tsx b/electron/src/setup/EthercatPage.tsx new file mode 100644 index 0000000..5527b9b --- /dev/null +++ b/electron/src/setup/EthercatPage.tsx @@ -0,0 +1,15 @@ +import { Page } from "@/components/Page"; +import { SectionTitle } from "@/components/SectionTitle"; +import React from "react"; + +export function EthercatPage() { + return ( + + +

+ Ethernet Interface{" "} + Discovering... +

+
+ ) +} \ No newline at end of file diff --git a/electron/src/setup/SetupPage.tsx b/electron/src/setup/SetupPage.tsx new file mode 100644 index 0000000..172636c --- /dev/null +++ b/electron/src/setup/SetupPage.tsx @@ -0,0 +1,18 @@ +import { Topbar } from "@/components/Topbar"; +import React from "react"; + +export function SetupPage() { + return ( + + ); +} \ No newline at end of file diff --git a/electron/src/types.d.ts b/electron/src/types.d.ts index 79df517..6235095 100644 --- a/electron/src/types.d.ts +++ b/electron/src/types.d.ts @@ -16,9 +16,19 @@ interface ElectronWindow { minimize: () => Promise; maximize: () => Promise; close: () => Promise; + fullscreen: (value: boolean) => Promise; +} + +interface EnvironmentInfo { + qitechOs: boolean; +} + +interface EnvironmentContext { + getInfo: () => Promise; } declare interface Window { themeMode: ThemeModeContext; electronWindow: ElectronWindow; + environment: EnvironmentContext; }