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 .github/workflows/frontend-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile --filter=testing-view --filter=flashing-view --filter=competition-view --filter=logging-view --filter=ui --filter=core
run: pnpm install --frozen-lockfile --filter=testing-view --filter=flashing-view --filter=competition-view --filter=logging-view --filter=adj-view --filter=ui --filter=core

- name: Build frontend
run: pnpm build --filter="./frontend/**"
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ on:
description: "Include Flashing View"
type: boolean
default: true
include-logging:
description: "Include Logging View"
type: boolean
default: true
include-adj:
description: "Include ADJ Viewer"
type: boolean
default: true


jobs:
Expand All @@ -34,6 +42,8 @@ jobs:
include_testing: ${{ steps.get_version.outputs.include_testing }}
include_competition: ${{ steps.get_version.outputs.include_competition }}
include_flashing: ${{ steps.get_version.outputs.include_flashing }}
include_logging: ${{ steps.get_version.outputs.include_logging }}
include_adj: ${{ steps.get_version.outputs.include_adj }}
steps:
- name: Determine version
id: get_version
Expand All @@ -43,6 +53,8 @@ jobs:
echo "include_testing=${{ github.event.inputs.include-testing }}" >> $GITHUB_OUTPUT
echo "include_competition=${{ github.event.inputs.include-competition }}" >> $GITHUB_OUTPUT
echo "include_flashing=${{ github.event.inputs.include-flashing }}" >> $GITHUB_OUTPUT
echo "include_logging=${{ github.event.inputs.include-logging }}" >> $GITHUB_OUTPUT
echo "include_adj=${{ github.event.inputs.include-adj }}" >> $GITHUB_OUTPUT

create-draft-release:
name: Create Draft Release
Expand Down Expand Up @@ -95,6 +107,12 @@ jobs:
if [ "${{ needs.determine-version.outputs.include_flashing }}" = "true" ]; then
FILTERS="$FILTERS --filter=flashing-view"
fi
if [ "${{ needs.determine-version.outputs.include_logging }}" = "true" ]; then
FILTERS="$FILTERS --filter=logging-view"
fi
if [ "${{ needs.determine-version.outputs.include_adj }}" = "true" ]; then
FILTERS="$FILTERS --filter=adj-view"
fi
pnpm turbo build $FILTERS

- uses: actions/upload-artifact@v4
Expand All @@ -118,6 +136,20 @@ jobs:
path: frontend/flashing-view/dist/**
retention-days: 1

- uses: actions/upload-artifact@v4
if: needs.determine-version.outputs.include_logging == 'true'
with:
name: logging-dist
path: frontend/logging-view/dist/**
retention-days: 1

- uses: actions/upload-artifact@v4
if: needs.determine-version.outputs.include_adj == 'true'
with:
name: adj-dist
path: frontend/adj-view/dist/**
retention-days: 1

build-backend:
name: Build Backend - ${{ matrix.os }}
needs: determine-version
Expand Down Expand Up @@ -300,6 +332,20 @@ jobs:
name: flashing-dist
path: electron-app/renderer/flashing-view

- name: Download logging-view dist
if: needs.determine-version.outputs.include_logging == 'true'
uses: actions/download-artifact@v4
with:
name: logging-dist
path: electron-app/renderer/logging-view

- name: Download adj-view dist
if: needs.determine-version.outputs.include_adj == 'true'
uses: actions/download-artifact@v4
with:
name: adj-dist
path: electron-app/renderer/adj-view

- uses: pnpm/action-setup@v4
with:
version: 10.26.0
Expand Down
1 change: 1 addition & 0 deletions electron-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ renderer/testing-view/
renderer/flashing-view/
renderer/competition-view/
renderer/logging-view/
renderer/adj-view/
# include mode-selector in the build output
!renderer/mode-selector/
!renderer/mode-selector/**
Expand Down
9 changes: 9 additions & 0 deletions electron-app/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ const CONFIG = {
"pnpm run build",
],
},
"adj-view": {
type: "frontend",
path: join(ROOT, "frontend/adj-view"),
dest: join(__dirname, "renderer/adj-view"),
commands: [
"pnpm --filter adj-view install --frozen-lockfile",
"pnpm run build",
],
},
};

// --- Helpers ---
Expand Down
1 change: 1 addition & 0 deletions electron-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"build:competition": "node build.mjs --competition-view",
"build:flashing": "node build.mjs --flashing-view",
"build:logging": "node build.mjs --logging-view",
"build:adj": "node build.mjs --adj-view",
"asar:win": "asar list dist/win-unpacked/resources/app.asar | findstr /V node_modules",
"asar:mac": "asar list dist/mac-unpacked/resources/app.asar | findstr /V node_modules",
"asar:linux": "asar list dist/linux-unpacked/resources/app.asar | findstr /V node_modules"
Expand Down
5 changes: 3 additions & 2 deletions electron-app/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("electronAPI", {
// Get the currently loaded view name
getCurrentView: () => ipcRenderer.invoke("get-current-view"),
// Switch to a different view by name
switchView: (view) => ipcRenderer.invoke("switch-view", view),
// Switch to a different view by name, optionally passing query params
// through to the loaded view's URL (e.g. an ADJ commit hash)
switchView: (view, query) => ipcRenderer.invoke("switch-view", view, query),
// Save configuration object to file
saveConfig: (config) => ipcRenderer.invoke("save-config", config),
// Get current configuration object
Expand Down
1 change: 1 addition & 0 deletions electron-app/renderer/mode-selector/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ <h1>Control Station</h1>
{ mode: 'competition', label: 'Competition View' },
{ mode: 'flashing', label: 'Flashing View' },
{ mode: 'logging', label: 'Logging View' },
{ mode: 'adj', label: 'ADJ Viewer' },
];

async function renderButtons() {
Expand Down
1 change: 1 addition & 0 deletions electron-app/src/app/modeSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const VALID_MODES = {
flashing: "flashing-view",
competition: "competition-view",
logging: "logging-view",
adj: "adj-view",
default: "testing-view",
};

Expand Down
7 changes: 5 additions & 2 deletions electron-app/src/ipc/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ function setupIpcHandlers() {
{ mode: "competition", label: "Competition View" },
{ mode: "flashing", label: "Flashing View" },
{ mode: "logging", label: "Logging View" },
{ mode: "adj", label: "ADJ Viewer" },
];
const rendererDir = join(getAppPath(), "renderer");
return ALL_VIEWS.filter(({ mode }) =>
Expand All @@ -117,10 +118,12 @@ function setupIpcHandlers() {
* @description Switches the main window to the specified view.
* @param {import("electron").IpcMainInvokeEvent} event - The IPC event object.
* @param {string} view - The name of the view to switch to (e.g., "ethernet-view", "control-station").
* @param {Record<string, string>} [query] - Optional query params passed through to the
* loaded view's URL (e.g. `{ commit: hash }` so adj-view can auto-load a commit).
* @returns {string} The view name that was loaded.
*/
ipcMain.handle("switch-view", (event, view) => {
loadView(view);
ipcMain.handle("switch-view", (event, view, query) => {
loadView(view, query ? { query } : undefined);
return view;
});

Expand Down
14 changes: 11 additions & 3 deletions electron-app/src/windows/mainWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,15 @@ function createWindow(screenWidth, screenHeight, initialView) {
/**
* Loads a specific view into the main window.
* @param {string} view - The name of the view to load (e.g., "ethernet-view", "control-station").
* @param {{ query?: Record<string, string> }} [opts] - Optional load options.
* `query` is appended as a query string (e.g. adj-view's initial commit hash),
* readable in the renderer via `new URLSearchParams(window.location.search)`.
* @returns {void}
* @example
* loadView("control-station");
* loadView("ethernet-view");
* loadView("adj-view", { query: { commit: "abc123" } });
*/
function loadView(view) {
function loadView(view, opts = {}) {
// Update current view tracking
currentView = view;
// Construct path to view HTML file
Expand All @@ -108,13 +111,18 @@ function loadView(view) {
// Check if view file exists
if (fs.existsSync(viewPath)) {
// Load the view HTML file
mainWindow.loadFile(viewPath);
if (opts.query) {
mainWindow.loadFile(viewPath, { query: opts.query });
} else {
mainWindow.loadFile(viewPath);
}
// Update window title based on view type
const titles = {
"competition-view": "Competition View",
"testing-view": "Testing View",
"flashing-view": "Flashing View",
"logging-view": "Logging View",
"adj-view": "ADJ Viewer",
};
mainWindow.setTitle(
`Hyperloop Control Station - ${titles[view] ?? view}`,
Expand Down
7 changes: 7 additions & 0 deletions frontend/adj-view/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const config = {
/** GitHub repository whose branches are offered when resolving a commit for the ADJ Viewer. */
ADJ_GITHUB_REPO: "hyperloop-upv/adj",

/** Timeout for fetching branches from GitHub API. */
BRANCHES_FETCH_TIMEOUT: 5000,
} as const;
3 changes: 3 additions & 0 deletions frontend/adj-view/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { config } from "@workspace/eslint-config/vite";

export default config;
15 changes: 15 additions & 0 deletions frontend/adj-view/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hyperloop UPV ADJ Viewer</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
31 changes: 31 additions & 0 deletions frontend/adj-view/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "adj-view",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.18",
"@vitejs/plugin-react-swc": "^4.2.3",
"@workspace/ui": "workspace:*",
"lucide-react": "^0.563.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwindcss": "^4.1.18"
},
"devDependencies": {
"@types/react": "19.2.11",
"@types/react-dom": "19.2.3",
"@workspace/eslint-config": "workspace:^",
"@workspace/typescript-config": "workspace:*",
"eslint": "^9.39.2",
"typescript": "^5.9.3",
"vite": "^7.3.1"
}
}
24 changes: 24 additions & 0 deletions frontend/adj-view/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useCallback, useEffect, useState } from "react";
import { AdjViewerPage } from "./components/AdjViewerPage";

export default function App() {
const [isDark, setIsDark] = useState(() => {
const saved = localStorage.getItem("adj-view-dark-mode");
return saved !== null
? saved === "true"
: window.matchMedia("(prefers-color-scheme: dark)").matches;
});

useEffect(() => {
document.documentElement.classList.toggle("dark", isDark);
localStorage.setItem("adj-view-dark-mode", String(isDark));
}, [isDark]);

const toggleTheme = useCallback(() => setIsDark((d) => !d), []);

return (
<div className="bg-background text-foreground h-screen w-screen overflow-auto">
<AdjViewerPage isDark={isDark} onToggleTheme={toggleTheme} />
</div>
);
}
Loading
Loading