Initial commit

This commit is contained in:
2023-10-29 20:58:28 -03:00
commit 486b77895c
19 changed files with 5187 additions and 0 deletions

20
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,20 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

8
README.md Normal file
View File

@@ -0,0 +1,8 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

17
index.html Normal file
View File

@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

4573
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "spectrometer-dash",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"preinstall": "npx npm-force-resolutions"
},
"dependencies": {
"antd": "^5.7.3",
"paho-mqtt": "^1.1.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"react-error-overlay": "^6.0.9",
"vite": "^4.4.5"
},
"resolutions": {
"react-error-overlay": "6.0.9"
}
}

1
public/vite.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

3
src/App.css Normal file
View File

@@ -0,0 +1,3 @@
.demo-logo h2 {
color: antiquewhite;
}

74
src/App.jsx Normal file
View File

@@ -0,0 +1,74 @@
import { useEffect, useState } from "react";
import ConnectForm from "./pages/Login";
import { Client } from "paho-mqtt";
import UnauthPage from "./layout/UnauthPage";
import { Spin } from "antd";
import Page from "./layout/Page";
import Spectrum from "./pages/Spectrum";
const App = () => {
const [client, setClient] = useState(null);
const [connected, setConnected] = useState(false);
const [connecting, setConnecting] = useState(false);
const [alert, setAlert] = useState(null);
const onConnect = (url, options) => {
console.log(`Trying to connect to ${url}...`);
const _client = new Client(url, options.clientId);
_client.onConnectionLost = onDisconnect;
_client.connect({
onSuccess: onConnected,
onFailure: onError,
userName: options.username,
password: options.password,
cleanSession: options.clean,
timeout: options.connectTimeout,
});
console.log('Options:', options);
setClient(_client);
setConnecting(true);
};
const onConnected = () => {
console.log("Connected!");
setConnecting(false);
setConnected(true);
};
const onError = (err) => {
console.error("Failed to connect", err);
setConnecting(false);
setAlert({ type: 'error', msg: `Could not connect, error: ${err.errorMessage}` });
};
const onDisconnect = () => {
client.disconnect();
};
useEffect(() => {
if (connecting)
setAlert(null);
}, [connecting]);
if (!connected)
return (
<UnauthPage content={
<ConnectForm
connect={onConnect}
btnContent={connecting ? <Spin /> : 'Connect'}
/>
} alert={alert} />
)
return (
<Page
content={<Spectrum />} />
)
}
export default App

65
src/ConnectedPage.jsx Normal file
View File

@@ -0,0 +1,65 @@
import { useState } from 'react';
import { Client } from 'paho-mqtt';
const ConnectedPage = () => {
const StateMessages = [
"Clique no botão para conectar",
"Conectando...",
"Conectado",
"Erro na conexão",
];
const [connected, setConnected] = useState(false);
const [client, setClient] = useState(null);
const [state, setState] = useState(0)
const handleClick = () => {
const options = {
clientId: 'aaaaa',
username: 'test',
password: '31415926',
clean: true,
reconnectPeriod: 1000,
connectTimeout: 30 * 1000,
host: 'fwmari.net',
port: 8883,
};
const _client = new Client(`ws://${options.host}:${options.port}/mqtt`, options.clientId);
_client.connect({
onSuccess: () => {
console.log("Conectado");
setConnected(true);
setState(2);
},
onFailure: (err) => {
console.log(`Erro ao conectar ${err}`);
setState(3);
},
});
setClient(_client);
setState(1);
}
return (
<div>
<button onClick={handleClick}>
Conectar ao servidor MQTT
</button>
<p>Status: {StateMessages[state]}</p>
{connected && (
<div>
{/* O resto da sua página vai aqui */}
<p>Conectado ao servidor MQTT! Agora você pode ver o resto da página.</p>
</div>
)}
</div>
);
}
export default ConnectedPage;

16
src/layout/Footer.jsx Normal file
View File

@@ -0,0 +1,16 @@
import { Layout } from "antd";
const { Footer } = Layout;
const SFooter = () => {
return (
<Footer
style={{
textAlign: 'center',
}}
>
Marisa © 2023
</Footer>
);
};
export default SFooter;

70
src/layout/Page.jsx Normal file
View File

@@ -0,0 +1,70 @@
import { Alert, Layout, Menu, theme } from 'antd';
import SFooter from './Footer';
import PropTypes from 'prop-types';
const { Header, Content } = Layout;
import '../App.css'
const menus = [
{
key: 'spectrum',
label: 'Espectro',
},
{
key: 'config',
label: 'Configuração',
},
{
key: 'about',
label: 'sobre',
}
];
const Page = ({ content, alert }) => {
const {
token: { colorBgContainer },
} = theme.useToken();
return (
<Layout className="layout">
<Header
style={{
display: 'flex',
alignItems: 'center',
}}
>
<div
className="demo-logo"
style={{ margin: '0 30px' }}
>
<h2>Spectrometer</h2>
</div>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['spectrum']}
items={menus}
/>
</Header>
<Content>
{alert && <Alert type={alert.type} message={alert.msg} banner />}
<div
className="site-layout-content"
style={{
padding: '16px 50px',
background: colorBgContainer,
}}
>
{content}
</div>
</Content>
<SFooter />
</Layout>
);
};
Page.propTypes = {
content: PropTypes.element.isRequired,
alert: PropTypes.object,
};
export default Page;

49
src/layout/UnauthPage.jsx Normal file
View File

@@ -0,0 +1,49 @@
import { Alert, Layout, theme } from "antd";
import PropTypes from 'prop-types';
const { Header, Content } = Layout;
import '../App.css'
import SFooter from "./Footer";
const UnauthPage = ({ content, alert }) => {
const {
token: { colorBgContainer },
} = theme.useToken();
return (
<Layout>
<Header
style={{
display: 'flex',
alignItems: 'center',
}}
>
<div
className="demo-logo"
style={{ margin: '0 30px' }}
>
<h2>Spectrometer</h2>
</div>
</Header>
<Content>
{alert && <Alert type={alert.type} message={alert.msg} banner />}
<div
className="site-layout-content"
style={{
padding: '16px 50px',
background: colorBgContainer,
}}
>
{content}
</div>
</Content>
<SFooter />
</Layout>
);
};
UnauthPage.propTypes = {
content: PropTypes.element.isRequired,
alert: PropTypes.object,
};
export default UnauthPage;

9
src/main.jsx Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

0
src/pages/Calib.jsx Normal file
View File

48
src/pages/Dashboard.jsx Normal file
View File

@@ -0,0 +1,48 @@
/**
* Main dashboard page
*/
import { useState } from "react";
const Dashboard = ({ messageHandlers, setMessageHandlers, sendMsg }) => {
const [motorStatus, setMotorStatus] = useState({});
const [encoderStatus, setEncoderStatus] = useState({});
const [ccdStatus, setCcdStatus] = useState({});
const onMessageArrived = (msg) => {
};
setMessageHandlers({
...messageHandlers,
dashboard: onMessageArrived,
});
const getMotorStatus = () => {
data = {
"type": "motor",
"command": "get_status",
};
sendMsg("/spectrometer/motor/getStatus", data);
};
const getEncoderStatus = () => {
data = {
"type": "encoder",
"command": "get_status",
};
sendMsg("/spectrometer/encoder/getStatus", data);
};
const getCcdStatus = () => {
data = {
"type": "ccd",
"command": "get_status",
};
sendMsg("/spectrometer/ccd/getStatus", data);
};
};
export default Dashboard;

103
src/pages/Login.jsx Normal file
View File

@@ -0,0 +1,103 @@
import PropTypes from 'prop-types'
import { Card, Button, Form, Input, Row, Col, Space } from 'antd'
const ConnectForm = ({ connect, btnContent }) => {
const [form] = Form.useForm()
const initialConnectionOptions = {
// ws or wss
protocol: 'ws',
host: 'fwmari.net',
clientId: `sp_${Math.random().toString(16).substring(2, 8)}`,
// ws -> 8083; wss -> 8084
port: 8883,
/**
* By default, EMQX allows clients to connect without authentication.
* https://docs.emqx.com/en/enterprise/v4.4/advanced/auth.html#anonymous-login
*/
username: 'test',
password: '31415926',
}
const onFinish = (values) => {
const { host, clientId, port, username, password } = values
const url = `ws://${host}:${port}/mqtt`
const options = {
clientId,
username,
password,
clean: true,
reconnectPeriod: 1000, // ms
connectTimeout: 30 * 1000, // ms
}
connect(url, options)
}
const handleConnect = () => {
form.submit()
}
const ConnectionForm = (
<Form
layout="vertical"
name="basic"
form={form}
initialValues={initialConnectionOptions}
onFinish={onFinish}
>
<Row gutter={20}>
<Col span={8}>
<Form.Item label="Host" name="host">
<Input />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="Port" name="port">
<Input />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="Client ID" name="clientId">
<Input disabled={true} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="Username" name="username">
<Input />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="Password" name="password">
<Input type='password' />
</Form.Item>
</Col>
</Row>
</Form>
)
return (
<Space
align='center'
style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}
>
<Card
title={<h2>Please, connect before using the system</h2>}
actions={[
<Button type="primary" onClick={handleConnect} key={'Connect'} ghost>
{btnContent}
</Button>
]}
>
{ConnectionForm}
</Card>
</Space>
)
}
ConnectForm.propTypes = {
connect: PropTypes.func.isRequired,
btnContent: PropTypes.oneOfType([
PropTypes.element, PropTypes.string,
]).isRequired
};
export default ConnectForm

66
src/pages/Spectrum.jsx Normal file
View File

@@ -0,0 +1,66 @@
import { Button, Card, Form, Input, Layout, Radio, theme } from "antd";
import { useState } from "react";
const { Content, Sider } = Layout;
const Spectrum = () => {
const {
token: { colorBgContainer },
} = theme.useToken();
const [form] = Form.useForm();
const [mode, setMode] = useState('Angle');
const initialValues = {
mode: "Angle",
};
const onFormChange = ({ mode }) => {
setMode(mode);
};
const angleForm = (
<Form
form={form}
onValuesChange={onFormChange}
initialValues={initialValues}
layout="horizontal"
size="small"
>
<Form.Item label="Mode" name="mode">
<Radio.Group value={mode}>
<Radio.Button value="Angle">Angle</Radio.Button>
<Radio.Button value="Wavelength">Wavelength</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label={`Target ${mode} (${mode == "Angle" ? '°' : 'Å'})`}>
<Input placeholder="30.127" />
</Form.Item>
</Form>
);
return (
<Layout style={{ height: 500 }}>
<Content>
<h2>kk eae men</h2>
</Content>
<Sider width={300} style={{ background: colorBgContainer }}>
<Card
title="Motor control"
size="small"
actions={[
<Button type="primary" key='sendAngle' size="small" ghost>
Send
</Button>
]}
>
{angleForm}
</Card>
</Sider>
</Layout>
)
};
export default Spectrum;

7
vite.config.js Normal file
View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})