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 (
+
+
+
+
+
+
)
}
\ 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;
}