How to Create a FrontPanel Application Project

This guide will show how to setup a project for a FrontPanel application. The application is created using Webpack, Typescript and the React user interface development library.

Install Prerequisites

Create the Application Project

Create a directory for the application project and set it to be the current directory.

mkdir frontpanel-app
cd frontpanel-appCode language: Bash (bash)

Create the package.json file using npm.

npm initCode language: Bash (bash)

This command should create a package.json file in the ‘frontpanel-alloy-app’ directory.

Setup Typescript

Install Typescript development dependencies.

npm install --save-dev typescript ts-node @types/nodeCode language: Bash (bash)

Create a tsconfig.json file in the project directory and add the following content.

{
  /* Visit https://aka.ms/tsconfig to read more about this file */
  "compilerOptions": {
    "target": "es6",
    "lib": [
      "dom",
      "dom.iterable",
      "es2023"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "jsx": "react-jsx",
    "sourceMap": true,
  },
  "include": [
    "src"
  ]
}Code language: JSON / JSON with Comments (json)

Setup Webpack Bundler

Install Webpack development dependencies.

npm install --save-dev webpack webpack-cli webpack-dev-server style-loader css-loader ts-loader html-webpack-plugin copy-webpack-plugin webpack-mergeCode language: Bash (bash)

Create webpack.common.js file in the project directory and add the following content.

const path = require('path');

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.tsx',
  plugins: [
    new HtmlWebpackPlugin({
      title: 'FrontPanel Application',
      template: path.resolve(__dirname, 'src/index.html'),
    }),
  ],
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(ico)$/i,
        type: 'asset/resource',
        generator: { 
          filename: 'assets/icons/[name][ext]'
        }
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
        generator: { 
          filename: 'assets/images/[name][ext]'
        }
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
        generator: { 
          filename: 'assets/fonts/[name][ext]'
        }
      },
      {
        test: /\.(bit)$/i,
        type: 'asset/resource',
        generator: { 
          filename: 'assets/bitfiles/[name][ext]'
        }
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
    modules: [path.resolve(__dirname, 'src'), path.resolve(__dirname, 'assets'), 'node_modules'],
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
};Code language: JavaScript (javascript)

Create webpack.dev.js file in the project directory and add the following content.

const { merge } = require('webpack-merge');

const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    static: './dist',
  },
});Code language: JavaScript (javascript)

Create webpack.prod.js file in the project directory and add the following content.

const { merge } = require('webpack-merge');

const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'production',
});Code language: JavaScript (javascript)

Edit the package.json file in the project directory so that the content of the “scripts” section is as follows.

"scripts": {
  "build:dev": "webpack --config webpack.dev.js",
  "build": "webpack --config webpack.prod.js",
  "start": "webpack serve --config webpack.dev.js",
  "test": "echo \"Error: no test specified\" && exit 1"
},Code language: JSON / JSON with Comments (json)

Setup React

Install React dependencies.

npm install react@^18.3.1 react-dom@^18.3.1Code language: Bash (bash)

Install React development dependencies.

npm install --save-dev @types/react@^18.3.12 @types/react-dom@^18.3.1Code language: Bash (bash)

Setup Asar

Install Asar development dependencies.

npm install --save-dev --engine-strict @electron/asar@^3.3.0Code language: Bash (bash)

Edit the package.json file in the project directory to add “pack:dev” and “pack” elements to the “scripts” section.

"scripts": {
    "pack:dev": "webpack --config webpack.dev.js && asar pack ./dist ./output/app-dev.asar",
    "pack": "webpack --config webpack.prod.js && asar pack ./dist ./output/app.asar",
    "build:dev": "webpack --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js",
    "start": "webpack serve --config webpack.dev.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },Code language: JSON / JSON with Comments (json)

Setup FrontPanel

Install FrontPanel React Components library dependency.

npm install @opalkelly/frontpanel-react-components@^0.4.0Code language: Bash (bash)

Add Application Sources

Create a src directory within the application project directory and add the following files.

src/index.css

body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
        "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

code {
    font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
}Code language: CSS (css)

src/index.tsx

import React from "react";

import ReactDOM from "react-dom/client";

import "./index.css";

//TODO: Replace 'frontpanel.bit' with the name of the configuration file.
//import "../assets/frontpanel.bit";

import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);

root.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);Code language: TypeScript (typescript)

src/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="<%=require("../assets/favicon.ico")%>" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="FrontPanel Alloy Application"
    />
    <title>FrontPanel Alloy Application</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>Code language: HTML, XML (xml)

src/App.css

.App {
    text-align: center;
    background-color: #282c34;
    min-height: 100vh;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    font-size: calc(10px + 2vmin);
    color: white;
}Code language: CSS (css)

src/App.tsx

import React from "react";

import "./App.css";

import FrontPanel from "./FrontPanel";

import {
    IDevice,
    IFrontPanel,
    WorkQueue,
    DataProgressCallback,
    ByteCount
} from "@opalkelly/frontpanel-alloy-core";

import { Application } from "@opalkelly/frontpanel-react-components";

import FrontPanelLogo from "../assets/logo512.png";

/**
 * Loads the specified configuration file into the FPGA on the specified device.
 * @param filename The name of the configuration file to load.
 * @param device The device to load the configuration file into.
 */
const loadConfiguration = async (filename: string, device: IDevice): Promise<void> => {
    console.log("Loading Configuration File:", filename);
    try {
        const response = await fetch("frontpanel://localhost/assets/bitfiles/" + filename);

        if (!response.ok) {
            throw new Error("Network response was not ok");
        }

        const arrayBuffer = await response.arrayBuffer();

        const reportProgress: DataProgressCallback = (total: ByteCount, completed: ByteCount) => {
            console.log("Configuration Progress: ", completed, " of ", total);
        };

        await device
            .getFPGA()
            .loadConfiguration(arrayBuffer, arrayBuffer.byteLength, reportProgress);

        console.log("Load Configuration Complete");
    } catch (error) {
        console.error("Failed to load configuration file:", filename, "\n", error);
    }
};

/**
 * Initializes a device by opening the specified device and
 * configuring the device with specified configuration file.
 * @param serialNumber The serial number of the device to open.
 * @param configurationFilename The name of the configuration file to load.
 * @returns {Promise<IDevice>} A promise that resolves to the opened device.
 */
const initializeDevice = async (
    serialNumber: string,
    configurationFilename: string
): Promise<IDevice> => {
    console.log("Opening Device...");

    const device = await window.FrontPanelAPI.deviceManager.openDevice(serialNumber);

    const deviceInfo = await device.getInfo();

    console.log("Opened Device:", deviceInfo.productName, " SerialNumber:", deviceInfo.serialNumber);

    await loadConfiguration(configurationFilename, device);

    return device;
};

const DeviceWorkQueue = new WorkQueue();

function App() {
    const [frontpanel, setFrontPanel] = React.useState<IFrontPanel>();

    React.useEffect(() => {
        let device: IDevice;

        DeviceWorkQueue.post(async () => {
            //TODO: Replace 'frontpanel.bit' with the name of the configuration file to load.
            device = await initializeDevice("", "frontpanel.bit");

            const frontpanel = await device.getFPGA().getFrontPanel();

            setFrontPanel(frontpanel);
        }).catch((error) => {
            device?.close();
            console.error("Failed to initialize the application:", error);
        });

        return () => {
            device?.close();
        };
    }, []);

    if (frontpanel !== undefined) {
        return (
            <div className="RootPanel">
                <Application>
                    <FrontPanel
                        name="Counters"
                        frontpanel={frontpanel}
                        workQueue={DeviceWorkQueue}
                    />
                </Application>
            </div>
        );
    } else {
        return (
            <div className="RootPanel">
                <img src={FrontPanelLogo} />
            </div>
        );
    }
}

export default App;Code language: JavaScript (javascript)

src/FrontPanel.css

.ControlPanel {
    display: flex;
    flex-direction: row;
    align-content: stretch;
    background-color: rgb(255, 255, 255);
    padding: 10px;
    margin: 10px;
    border-radius: 8px;
}Code language: CSS (css)

src/FrontPanel.tsx

import { Component } from "react";

import {
    IFrontPanel,
    WorkQueue
} from "@opalkelly/frontpanel-alloy-core";

import "./FrontPanel.css";

export interface FrontPanelProps {
    name: string;
    frontpanel: IFrontPanel;
    workQueue: WorkQueue;
}

class FrontPanel extends Component<FrontPanelProps> {
    // Constructor
    constructor(props: FrontPanelProps) {
        super(props);
    }

    // Component Lifecycle Methods
    componentDidMount() {
    }

    componentWillUnmount() {
    }

    render() {
        return (
            <div>TODO: Add JSX here</div>
        );
    }
}

export default FrontPanel;Code language: JavaScript (javascript)

src/declarations.d.ts

declare module "*.png" {
    const value: string;
    export default value;
}Code language: TypeScript (typescript)

Add Application Assets

Create a assets directory within the application project directory.

Download the following image and place it in the assets directory.

Build and Package the Application

Create the production application package by running.

npm run packCode language: Bash (bash)

Create the development application package by running:

npm run pack:devCode language: Bash (bash)

Run the Application

Launch the FrontPanel Platform application and select the output/app.asar or output/app-dev.asar file.