Skip to content
Draft
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
23 changes: 22 additions & 1 deletion ui/app/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
Custom UI apps can be added here use the manager UI app as a template.
# Custom UI apps

In this folder, you can add custom (web) applications that will be shipped along with OpenRemote.
For example, special mobile apps for end users, or apps for less-technical consumers are widespread.

Developing these custom apps is pretty straightforward, thanks to the built-in packages we provide.
These make communicating with OpenRemote easier, and allows developers to quickly set up an user interface.

## Example apps

We provided several example apps to get familiar with the architecture.
All apps can be ran using `npm run serve`, and visited at http://localhost:9000/custom/.
Here's a list of the apps, and what they do;

### /custom
This is an example web application built with [Lit Web Components](https://lit.dev) and [Webpack](https://webpack.js.org).
Apps in our main OpenRemote [repository](https://github.com/openremote/openremote) are built with these technologies as well.
It can be used as a template to add your own pages on top of it.

### /custom-react
This is an example web application built with [React 19](https://react.dev) and [RSPack](https://rspack.rs).
*(more information soon)*
13 changes: 13 additions & 0 deletions ui/app/custom-react/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Local
.DS_Store
*.local
*.log*

# Dist
node_modules
dist/

# IDE
.vscode/*
!.vscode/extensions.json
.idea
12 changes: 12 additions & 0 deletions ui/app/custom-react/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
buildDir = "dist"

tasks.register('clean') {
dependsOn npmClean
}

tasks.register('installDist', Copy) {
dependsOn npmBuild
mustRunAfter(resolveTask(":manager:installDist"))
from project.buildDir
into "${project(':deployment').buildDir}/image/manager/app/${projectDir.name}"
}
12 changes: 12 additions & 0 deletions ui/app/custom-react/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/react.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Custom app example using React</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
30 changes: 30 additions & 0 deletions ui/app/custom-react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@openremote/custom-react",
"version": "1.0.0",
"description": "OpenRemote Custom App using React",
"author": "OpenRemote",
"license": "AGPL-3.0-or-later",
"private": true,
"scripts": {
"clean": "npx tsc -b --clean && npx shx rm -rf dist",
"build": "npx cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 rspack build",
"serve": "npx cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 rspack serve"
},
"dependencies": {
"@openremote/core": "^1.9.0",
"@openremote/model": "^1.9.0",
"@openremote/or-mwc-components": "^1.9.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@rspack/cli": "^1.5.8",
"@rspack/core": "^1.5.8",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"cross-env": "^10.1.0",
"react-refresh": "^0.18.0",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
}
}
74 changes: 74 additions & 0 deletions ui/app/custom-react/rspack.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { defineConfig } from "@rspack/cli";
import { rspack } from "@rspack/core";

const isDev = process.env.NODE_ENV === "development";

// Target browsers, see: https://github.com/browserslist/browserslist
const targets = ["chrome >= 87", "edge >= 88", "firefox >= 78", "safari >= 14"];

export default defineConfig({
context: __dirname,
devServer: {
host: "0.0.0.0",
port: 9000,
open: false
},
entry: {
main: "./src/main.tsx"
},
resolve: {
extensions: ["...", ".ts", ".tsx", ".jsx"]
},
module: {
rules: [
{
test: /\.svg$/,
type: "asset"
},
{
test: /(maplibre|mapbox|@material|gridstack|@mdi).*\.css$/, //output css as strings
type: "asset/source"
},
{
test: /\.tsx$/,
type: "javascript/auto",
use: [
{
loader: "builtin:swc-loader",
options: {
jsc: {
parser: {
syntax: "typescript",
tsx: true
}
}
}
}
]
}
]
},
plugins: [
new rspack.HtmlRspackPlugin({
template: "./index.html"
}),
// Define MANAGER_URL as a global variable
new rspack.DefinePlugin({
MANAGER_URL: JSON.stringify(process.env.MANAGER_URL ?? (isDev ? "http://localhost:8080" : undefined))
})
],
optimization: {
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin(),
new rspack.LightningCssMinimizerRspackPlugin({
minimizerOptions: { targets }
})
]
},
output: {
publicPath: isDev ? "/custom-react/" : "/",
},
experiments: {
css: true
}
});
10 changes: 10 additions & 0 deletions ui/app/custom-react/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 12em;
}
23 changes: 23 additions & 0 deletions ui/app/custom-react/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";
import openremoteLogo from "./assets/openremote.svg";
import {InputType} from "@openremote/or-mwc-components/or-mwc-input";
import type {} from "@openremote/or-mwc-components/jsx";
import "./App.css";

function App() {
return (
<div className="App">
<div id="logo">
<img src={openremoteLogo} className="logo openremote" alt="OpenRemote logo"/>
</div>
<h2>"A React template for your custom app."</h2>
<div id="content">
<a href="https://docs.openremote.io" target="_blank" rel="noreferrer">
<or-mwc-input type={InputType.BUTTON} outlined label="View the documentation"></or-mwc-input>
</a>
</div>
</div>
);
}

export default App;
16 changes: 16 additions & 0 deletions ui/app/custom-react/src/assets/openremote.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions ui/app/custom-react/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;

color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;

font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}

@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
}
35 changes: 35 additions & 0 deletions ui/app/custom-react/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import {manager} from "@openremote/core";
import {ManagerConfig} from "@openremote/model";
import "./index.css";

declare const MANAGER_URL: string | undefined;

/**
* Define the Manager configuration to talk with OpenRemote.
* For example, defining the realm and URL to communicate with. (these will be consumed with HTTP API calls for example)
* We also enable autoLogin to prompt a Keycloak login before the app appears.
*/
const managerConfig: ManagerConfig = {
realm: "master",
managerUrl: MANAGER_URL ?? "",
autoLogin: true
};

/**
* Initialize the Manager connection.
* Afterward, we can start rendering the React DOM UI.
*/
manager.init(managerConfig).then(() => {

/**
* Render your React application to the DOM.
*/
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
});
Loading
Loading