diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 19c359b..0000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -**/.eslintrc.js -doc/ -frontend/ -scripts/ \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 6e461e9..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "root": true, - "env": { - "es6": true, - "node": true, - "mocha": true - }, - "extends": [ - "eslint:recommended" - ], - "plugins": [], - "rules": { - "indent": [ - "error", - 4, - { - "SwitchCase": 1 - } - ], - "no-console": "off", - "no-unused-vars": [ - "error", - { - "ignoreRestSiblings": true, - "argsIgnorePattern": "^_" - } - ], - "no-var": "error", - "no-trailing-spaces": "error", - "prefer-const": "error", - "quotes": [ - "error", - "single", - { - "avoidEscape": true, - "allowTemplateLiterals": true - } - ], - "semi": [ - "error", - "always" - ] - }, - "parserOptions": { - "ecmaVersion": "latest" - } -} \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 515bcd4..0000000 --- a/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -package.json -package-lock.json \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 164ee34..0000000 --- a/.prettierrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - semi: true, - trailingComma: 'all', - singleQuote: true, - printWidth: 120, - useTabs: false, - tabWidth: 4, - endOfLine: 'lf', -}; diff --git a/DEBUGGING.md b/DEBUGGING.md index f5b5303..a2a5e26 100644 --- a/DEBUGGING.md +++ b/DEBUGGING.md @@ -2,19 +2,19 @@ ## Local Debugging -To test the repochecker and debug into the script under vscode: +To test the adapter-checker and debug into the script under vscode: 1. Clone the repository to your local machine.\ - Best is that the directory is on the same level than your adapter repository. + Best is that the directory is on the same level as your adapter repository. -2. run npm install in the repochecker directory. +2. run npm install in the adapter-checker directory. 3. switch to your adapter repository and create a new launch configuration: ```json5 { "name": "Launch Program", - "program": "../iobroker.repochecker/index.js", // path to the repochecker repo + "program": "../iobroker.repochecker/index.js", // path to the adapter checker repo // args as entered on the commandline, arguments as a array "args": ["https://github.com/klein0r/ioBroker.luftdaten","--local"], "request": "launch", @@ -29,12 +29,12 @@ To test the repochecker and debug into the script under vscode: ## Local Testing without debugging -To test the repochecker under vscode: +To test the adapter checker under vscode: 1. Clone the repository to your local machine.\ - Best is that the directory is on the same level than your adapter repository. + Best is that the directory is on the same level as your adapter repository. -2. run npm install in the repochecker directory. +2. run npm install in the adapter checker directory. 3. switch to your adapter repository and enter the following commandline in a new terminal diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..e026127 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,27 @@ +import config from '@iobroker/eslint-config'; + +export default [ + ...config, + { + languageOptions: { + parserOptions: { + allowDefaultProject: { + allow: ['*.js', '*.mjs'], + }, + tsconfigRootDir: import.meta.dirname, + project: './tsconfig.json', + // projectService: true, + }, + }, + }, + { + // disable temporary the rule 'jsdoc/require-param' and enable 'jsdoc/require-jsdoc' + rules: { + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-param': 'off', + }, + }, + { + ignores: ['build-backend/**/*', 'lib/**/*'], + }, +]; diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000..ea48839 --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,34 @@ +import config, { reactConfig } from '@iobroker/eslint-config'; + +export default [ + ...config, + ...reactConfig, + { + rules: { + 'no-new-func': 'warn', + 'no-extend-native': 'warn', + 'no-eval': 'warn', + }, + }, + { + languageOptions: { + parserOptions: { + projectService: { + allowDefaultProject: ['*.mjs'], + }, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + // disable temporary the rule 'jsdoc/require-param' and enable 'jsdoc/require-jsdoc' + rules: { + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-param': 'off', + '@/no-duplicate-imports': 'error', + }, + }, + { + ignores: ['build/**/*', 'node_modules/**/*', 'src/serviceWorker.js', 'vite.config.mjs'], + }, +]; diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..850eae7 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,31 @@ + + + + + + + + + + ioBroker Adapter checker + + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json index 01d8be2..bffb1c0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,32 +1,30 @@ { - "name": "frontend-user", - "version": "0.1.0", - "private": true, - "dependencies": { - "@iobroker/adapter-react-v5": "^4.0.2", - "@mui/material": "^5.10.17", - "@mui/icons-material": "^5.10.16", - "@mui/styles": "^5.10.16", - "babel-eslint": "^10.1.0", - "react-moment": "^1.1.2", - "moment": "^2.29.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-scripts": "^5.0.1" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": [ - ">0.2%", - "not dead", - "not ie <= 11", - "not op_mini all" - ] + "name": "frontend-user", + "version": "0.1.0", + "private": true, + "dependencies": { + "@iobroker/adapter-react-v5": "^7.4.14", + "@mui/material": "^6.4.0", + "@mui/icons-material": "^6.4.0", + "@vitejs/plugin-react": "^4.3.4", + "react-moment": "^1.1.3", + "moment": "^2.30.1", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "scripts": { + "start": "vite", + "build": "vite build", + "lint": "eslint -c eslint.config.mjs", + "npm": "npm i --force" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] } diff --git a/frontend/public/index.html b/frontend/public/index.html deleted file mode 100644 index 49b24ff..0000000 --- a/frontend/public/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - ioBroker Adapter checker - - - -
- - diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json index bea54e1..b501703 100644 --- a/frontend/public/manifest.json +++ b/frontend/public/manifest.json @@ -1,15 +1,15 @@ { - "short_name": "Repo checker", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" + "short_name": "Adapter checker", + "name": "ioBroker Adapter checker", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" } diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index 748abd5..0000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,2 +0,0 @@ -.App { -} diff --git a/frontend/src/App.js b/frontend/src/App.js deleted file mode 100644 index 5a51ef7..0000000 --- a/frontend/src/App.js +++ /dev/null @@ -1,343 +0,0 @@ -import React, {Component} from 'react'; -import { withStyles } from '@mui/styles'; -import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles'; - -import './App.css'; -import AppBar from '@mui/material/AppBar'; -import Toolbar from '@mui/material/Toolbar'; -import Input from '@mui/material/Input'; -import Fab from '@mui/material/Fab'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; -import Button from '@mui/material/Button'; -import CircularProgress from '@mui/material/CircularProgress'; - -import CheckIcon from '@mui/icons-material/DoneOutlined'; -import ErrorIcon from '@mui/icons-material/Cancel'; -import WarningIcon from '@mui/icons-material/Announcement'; - -import Comm from './Comm'; -import ToggleThemeMenu from './ToggleThemeMenu'; -import Utils from '@iobroker/adapter-react-v5/Components/Utils'; -import theme from '@iobroker/adapter-react-v5/Theme'; -import MessageDialog from '@iobroker/adapter-react-v5/Dialogs/Message'; -import I18n from '@iobroker/adapter-react-v5/i18n'; - -const NARROW_WIDTH = 500; - -const styles = theme => ({ - toolbarTitle: { - //position: 'absolute', - top: 0, - right: 20, - whiteSpace: 'nowrap' - }, - urlInput: { - color: 'white' - }, - branchInput: { - width: 100, - marginLeft: 10, - color: 'white' - }, - attrTitle: { - display: 'inline-block', - width: 160, - fontWeight: 'bold', - paddingLeft: 10 - }, - title: { - background: '#faff7c', - padding: 5, - marginTop: 10, - marginBottom: 0 - }, - buttonCheck: { - marginLeft: 10, - marginRight: 20, - }, - body: { - width: '100%', - height: '100%', - overflow: 'hidden', - }, - info: { - padding: 20, - overflow: 'auto', - height: 'calc(100% - 104px)' - }, - ok: { - color: '#111', - }, - error: { - color: '#bf0000' - }, - warning: { - color: '#bf9100' - }, -}); - -class App extends Component { - constructor(props) { - super(props); - - const _theme = theme(Utils.getThemeName()); - - const translations = { - 'en': require('@iobroker/adapter-react-v5/i18n/en'), - 'de': require('@iobroker/adapter-react-v5/i18n/de'), - 'ru': require('@iobroker/adapter-react-v5/i18n/ru'), - 'pt': require('@iobroker/adapter-react-v5/i18n/pt'), - 'nl': require('@iobroker/adapter-react-v5/i18n/nl'), - 'fr': require('@iobroker/adapter-react-v5/i18n/fr'), - 'it': require('@iobroker/adapter-react-v5/i18n/it'), - 'es': require('@iobroker/adapter-react-v5/i18n/es'), - 'pl': require('@iobroker/adapter-react-v5/i18n/pl'), - 'zh-cn': require('@iobroker/adapter-react-v5/i18n/zh-cn'), - }; - - I18n.setTranslations(translations); - I18n.setLanguage((navigator.language || navigator.userLanguage || 'en').substring(0, 2).toLowerCase()); - - this.state = { - url: window.localStorage.getItem('url') || '', - requesting: false, - errors: [], - warnings: [], - result: [], - screenWidth: window.innerWidth, - version: 'Adapter checker', - branch: window.localStorage.getItem('branch') || '', - theme: _theme, - themeName: _theme.name, - themeType: _theme.palette.type, - hasTravis: false, - globalError: null, - }; - - if (window.document.location.search) { - const query = window.document.location.search.replace(/^\?/, ''); - const pairs = query.split('&'); - pairs.forEach(pair => { - const parts = pair.split('='); - if (parts[0] === 'q' && parts[1]) { - this.state.url = decodeURIComponent(parts[1]); - } - }); - - setTimeout(() => this.onCheck(), 500); - } - - this.updateWindowDimensions = this.updateWindowDimensions.bind(this); - } - - componentDidMount() { - window.addEventListener('resize', this.updateWindowDimensions()); - } - - componentWillUnmount() { - window.removeEventListener('resize', this.updateWindowDimensions) - } - - updateWindowDimensions() { - this.setState({screenWidth: window.innerWidth}); - } - - onCheck() { - let url = this.state.url; - if (url.match(/\/$/, '')) { - url = url.substring(0, url.length - 1); - } - - this.setState({errors: [], result: [], warnings:[], requesting: true}); - - Comm.check(url, this.state.branch.trim(), (err, data) => { - if (err) { - this.setState({ - errors: data.errors || [], - globalError: err, - warnings: (data && data.warnings) || [], - result: (data && data.checks) || [], - requesting: false, - hasTravis: (data && data.hasTravis) || false, - }); - } else { - this.setState({ - errors: data.errors || [], - warnings: data.warnings || [], - result: data.checks || [], - version: 'v' + data.version, - requesting: false, - hasTravis: data.hasTravis || false, - }); - } - }); - } - - renderResult() { - return - {this.state.result.map((line, i) => - - - - - )} - ; - } - - renderError() { - return - {this.state.errors.length ?
: null} - {this.state.errors.map((line, i) => - - - - - )} -
; - } - - renderWarnings() { - return - {this.state.warnings.map((line, i) => - - - - - )} - ; - } - - onOpen(path) { - let url = this.state.url.replace('https://raw.githubusercontent.com/', 'https://github.com/'); - url = url.replace(/\/$/, '') + path; - const win = window.open(url, '_blank'); - win.focus(); - } - - onOpenLink(href) { - const win = window.open(href, '_blank'); - win.focus(); - } - - onOpenTravis() { - let url = this.state.url.replace('https://raw.githubusercontent.com/', 'https://travis-ci.org/'); - url = url.replace(/\/$/, ''); - const win = window.open(url, '_blank'); - win.focus(); - } - - toggleTheme() { - const themeName = this.state.themeName; - - // dark => blue => colored => light => dark - let newThemeName = themeName === 'dark' ? 'blue' : - (themeName === 'blue' ? 'colored' : - (themeName === 'colored' ? 'light' : 'dark')); - - Utils.setThemeName(newThemeName); - - const _theme = theme(newThemeName); - - this.setState({ - theme: _theme, - themeName: _theme.name, - themeType: _theme.palette.type - }); - } - - showError() { - if (this.state.globalError) { - return this.setState({globalError: null})} - title={this.state.globalError} - />; - } else { - return null; - } - } - - render() { - const narrowScreen = this.state.screenWidth <= NARROW_WIDTH; - - return - -
- - - { - if (e.key === 'Enter' && this.state.url && !this.state.requesting) { - this.onCheck(); - } - }} - readOnly={this.state.requesting} - className={this.props.classes.urlInput} - style={{maxWidth: narrowScreen ? this.state.screenWidth - 35 : this.state.screenWidth - 250, width: narrowScreen ? 'calc(100% - 35px)' : 'calc(100% - 350px)'}} - onChange={e => { - window.localStorage.setItem('url', e.target.value); - this.setState({url: e.target.value}); - }} - /> - {!narrowScreen ? { - if (e.key === 'Enter' && this.state.url && !this.state.requesting) { - this.onCheck(); - } - }} - readOnly={this.state.requesting} - className={this.props.classes.branchInput} - onChange={e => { - window.localStorage.setItem('branch', e.target.value); - this.setState({branch: e.target.value}); - }} - /> : null} - { - this.state.requesting ? - : - this.onCheck()} aria-label="Check"> - } - {!narrowScreen ?

{this.state.version}

: null} - {!narrowScreen ? this.toggleTheme()} - themeName={this.state.themeName} - t={w => w} - /> : null} -
-
-
- {this.state.result.length ? [ - , - , - , - this.state.hasTravis ? : null, - this.state.errors && this.state.errors.length ? : null, - ] : null} - {this.state.errors ? this.renderError() : null} - {this.state.warnings ? this.renderWarnings() : null} - {this.state.result ? this.renderResult() : null} -
- {this.showError()} -
-
-
; - } -} - -export default withStyles(styles)(App); diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..661a981 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,450 @@ +import React, { Component } from 'react'; +import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles'; + +import { + AppBar, + Toolbar, + Input, + Fab, + List, + ListItem, + ListItemIcon, + ListItemText, + Button, + CircularProgress, +} from '@mui/material'; + +import { DoneOutlined as CheckIcon, Cancel as ErrorIcon, Announcement as WarningIcon } from '@mui/icons-material'; + +import { Utils, Theme, DialogMessage, I18n } from '@iobroker/adapter-react-v5'; + +import Comm from './Comm'; +import ToggleThemeMenu from './ToggleThemeMenu'; + +import en from '@iobroker/adapter-react-v5/i18n/en'; +import de from '@iobroker/adapter-react-v5/i18n/de'; +import ru from '@iobroker/adapter-react-v5/i18n/ru'; +import pt from '@iobroker/adapter-react-v5/i18n/pt'; +import nl from '@iobroker/adapter-react-v5/i18n/nl'; +import fr from '@iobroker/adapter-react-v5/i18n/fr'; +import it from '@iobroker/adapter-react-v5/i18n/it'; +import es from '@iobroker/adapter-react-v5/i18n/es'; +import pl from '@iobroker/adapter-react-v5/i18n/pl'; +import zhCN from '@iobroker/adapter-react-v5/i18n/zh-cn'; + +const NARROW_WIDTH = 500; + +const styles = { + toolbarTitle: { + top: 0, + right: 20, + whiteSpace: 'nowrap', + }, + urlInput: { + color: 'white', + }, + branchInput: { + width: 100, + marginLeft: 10, + color: 'white', + }, + attrTitle: { + display: 'inline-block', + width: 160, + fontWeight: 'bold', + paddingLeft: 10, + }, + title: { + background: '#faff7c', + padding: 5, + marginTop: 10, + marginBottom: 0, + }, + buttonCheck: { + marginLeft: 10, + marginRight: 20, + }, + body: { + width: '100%', + height: '100%', + overflow: 'hidden', + }, + info: { + padding: 20, + overflow: 'auto', + height: 'calc(100% - 104px)', + }, + ok: { + color: '#111', + }, + error: { + color: '#bf0000', + }, + warning: { + color: '#bf9100', + }, +}; + +class App extends Component { + constructor(props) { + super(props); + + const theme = Theme(Utils.getThemeName()); + + const translations = { + en, + de, + ru, + pt, + nl, + fr, + it, + es, + pl, + 'zh-cn': zhCN, + }; + + I18n.setTranslations(translations); + I18n.setLanguage((navigator.language || navigator.userLanguage || 'en').substring(0, 2).toLowerCase()); + + this.state = { + url: window.localStorage.getItem('url') || '', + requesting: false, + errors: [], + warnings: [], + result: [], + screenWidth: window.innerWidth, + version: 'Adapter checker', + branch: window.localStorage.getItem('branch') || '', + theme, + themeName: theme.name, + themeType: theme.palette.mode, + hasTravis: false, + globalError: null, + }; + + if (window.document.location.search) { + const query = window.document.location.search.replace(/^\?/, ''); + const pairs = query.split('&'); + pairs.forEach(pair => { + const parts = pair.split('='); + if (parts[0] === 'q' && parts[1]) { + this.state.url = decodeURIComponent(parts[1]); + } + }); + + setTimeout(() => this.onCheck(), 500); + } + + this.updateWindowDimensions = this.updateWindowDimensions.bind(this); + if (theme.palette.mode === 'dark') { + window.document.body.style.background = '#111'; + } + } + + componentDidMount() { + window.addEventListener('resize', this.updateWindowDimensions()); + } + + componentWillUnmount() { + window.removeEventListener('resize', this.updateWindowDimensions); + } + + updateWindowDimensions() { + this.setState({ screenWidth: window.innerWidth }); + } + + onCheck() { + let url = this.state.url; + if (url.match(/\/$/, '')) { + url = url.substring(0, url.length - 1); + } + + this.setState({ errors: [], result: [], warnings: [], requesting: true }); + + Comm.check(url, this.state.branch.trim(), (err, data) => { + if (err) { + this.setState({ + errors: data.errors || [], + globalError: err, + warnings: (data && data.warnings) || [], + result: (data && data.checks) || [], + requesting: false, + hasTravis: (data && data.hasTravis) || false, + }); + } else { + this.setState({ + errors: data.errors || [], + warnings: data.warnings || [], + result: data.checks || [], + version: 'v' + data.version, + requesting: false, + hasTravis: data.hasTravis || false, + }); + } + }); + } + + renderResult() { + return ( + + {this.state.result.map((line, i) => ( + + + + + + + ))} + + ); + } + + renderError() { + return ( + + {this.state.errors.length ?
: null} + {this.state.errors.map((line, i) => ( + + + + + + + ))} +
+ ); + } + + renderWarnings() { + return ( + + {this.state.warnings.map((line, i) => ( + + + + + + + ))} + + ); + } + + onOpen(path) { + let url = this.state.url.replace('https://raw.githubusercontent.com/', 'https://github.com/'); + url = url.replace(/\/$/, '') + path; + const win = window.open(url, '_blank'); + win.focus(); + } + + onOpenLink(href) { + const win = window.open(href, '_blank'); + win.focus(); + } + + onOpenTravis() { + let url = this.state.url.replace('https://raw.githubusercontent.com/', 'https://travis-ci.org/'); + url = url.replace(/\/$/, ''); + const win = window.open(url, '_blank'); + win.focus(); + } + + toggleTheme() { + const themeName = this.state.themeName; + + // dark => blue => colored => light => dark + let newThemeName = themeName === 'dark' ? 'light' : 'dark'; + + Utils.setThemeName(newThemeName); + + const theme = Theme(newThemeName); + + if (theme.palette.mode === 'dark') { + window.document.body.style.background = '#111'; + } else { + window.document.body.style.background = '#EEE'; + } + this.setState({ + theme, + themeName: theme.name, + themeType: theme.palette.type, + }); + } + + showError() { + if (this.state.globalError) { + return ( + this.setState({ globalError: null })} + text={this.state.globalError} + /> + ); + } else { + return null; + } + } + + render() { + const narrowScreen = this.state.screenWidth <= NARROW_WIDTH; + + return ( + + +
+ + + { + if (e.key === 'Enter' && this.state.url && !this.state.requesting) { + this.onCheck(); + } + }} + readOnly={this.state.requesting} + style={{ + ...styles.urlInput, + maxWidth: narrowScreen + ? this.state.screenWidth - 35 + : this.state.screenWidth - 250, + width: narrowScreen ? 'calc(100% - 35px)' : 'calc(100% - 350px)', + }} + onChange={e => { + window.localStorage.setItem('url', e.target.value); + this.setState({ url: e.target.value }); + }} + /> + {!narrowScreen ? ( + { + if (e.key === 'Enter' && this.state.url && !this.state.requesting) { + this.onCheck(); + } + }} + readOnly={this.state.requesting} + style={styles.branchInput} + onChange={e => { + window.localStorage.setItem('branch', e.target.value); + this.setState({ branch: e.target.value }); + }} + /> + ) : null} + {this.state.requesting ? ( + + ) : ( + this.onCheck()} + aria-label="Check" + > + + + )} + {!narrowScreen ?

{this.state.version}

: null} + {!narrowScreen ? ( + this.toggleTheme()} + themeName={this.state.themeName} + t={w => w} + /> + ) : null} +
+
+
+ {this.state.result.length + ? [ + , + , + , + this.state.hasTravis ? ( + + ) : null, + this.state.errors && this.state.errors.length ? ( + + ) : null, + ] + : null} + {this.state.errors ? this.renderError() : null} + {this.state.warnings ? this.renderWarnings() : null} + {this.state.result ? this.renderResult() : null} +
+ {this.showError()} +
+
+
+ ); + } +} + +export default App; diff --git a/frontend/src/App.test.js b/frontend/src/App.test.js deleted file mode 100644 index a754b20..0000000 --- a/frontend/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/frontend/src/Comm.js b/frontend/src/Comm.js index 5ea80ba..81d24e8 100644 --- a/frontend/src/Comm.js +++ b/frontend/src/Comm.js @@ -1,4 +1,7 @@ -const URL = window.API_URL && window.API_URL !== '${API_URL}' ? `${window.API_URL}` : 'https://3jjxddo33l.execute-api.eu-west-1.amazonaws.com/default/checkAdapter'; +const URL = + window.API_URL && window.API_URL !== '${API_URL}' + ? `${window.API_URL}` + : 'https://3jjxddo33l.execute-api.eu-west-1.amazonaws.com/default/checkAdapter'; class Comm { static check(repo, branch, cb) { const url = `${URL}?url=${encodeURIComponent(repo)}${branch ? `&branch=${encodeURIComponent(branch)}` : ''}`; @@ -7,7 +10,7 @@ class Comm { .then(res => res.json()) .then( result => cb && cb(result.error || null, result), - error => cb && cb(error) + error => cb && cb(error), ); } catch (error) { cb && cb(error); @@ -15,4 +18,4 @@ class Comm { } } -export default Comm; \ No newline at end of file +export default Comm; diff --git a/frontend/src/ToggleThemeMenu.js b/frontend/src/ToggleThemeMenu.js deleted file mode 100644 index a779834..0000000 --- a/frontend/src/ToggleThemeMenu.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { IconButton, Tooltip } from '@mui/material'; -import Brightness4Icon from '@mui/icons-material/Brightness4'; -import Brightness5Icon from '@mui/icons-material/Brightness5'; -import Brightness6Icon from '@mui/icons-material/Brightness6'; -import Brightness7Icon from '@mui/icons-material/Brightness7'; - -export default function ToggleThemeMenu({ themeName, toggleTheme, t, className, style, size }) { - return
- - toggleTheme()} size={size || 'medium'}> - {themeName === 'dark' && } - {themeName === 'blue' && } - {themeName === 'colored' && } - {themeName === 'light' && } - - -
; -} \ No newline at end of file diff --git a/frontend/src/ToggleThemeMenu.jsx b/frontend/src/ToggleThemeMenu.jsx new file mode 100644 index 0000000..317eb48 --- /dev/null +++ b/frontend/src/ToggleThemeMenu.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { IconButton, Tooltip } from '@mui/material'; +import { Brightness4 as Brightness4Icon, Brightness7 as Brightness7Icon } from '@mui/icons-material'; + +export default function ToggleThemeMenu({ themeName, toggleTheme, t, className, style, size }) { + return ( +
+ + toggleTheme()} + size={size || 'medium'} + > + {themeName === 'dark' && } + {themeName === 'light' && } + + +
+ ); +} diff --git a/frontend/src/assets/alexaLogo.png b/frontend/src/assets/alexaLogo.png deleted file mode 100644 index 8616b12..0000000 Binary files a/frontend/src/assets/alexaLogo.png and /dev/null differ diff --git a/frontend/src/assets/knx.png b/frontend/src/assets/knx.png deleted file mode 100644 index 267ed3f..0000000 Binary files a/frontend/src/assets/knx.png and /dev/null differ diff --git a/frontend/src/assets/lcn.png b/frontend/src/assets/lcn.png deleted file mode 100644 index 9f589d4..0000000 Binary files a/frontend/src/assets/lcn.png and /dev/null differ diff --git a/frontend/src/assets/remote.png b/frontend/src/assets/remote.png deleted file mode 100644 index 9bfc7ae..0000000 Binary files a/frontend/src/assets/remote.png and /dev/null differ diff --git a/frontend/src/assets/vis.png b/frontend/src/assets/vis.png deleted file mode 100644 index f52bcd0..0000000 Binary files a/frontend/src/assets/vis.png and /dev/null differ diff --git a/frontend/src/index.css b/frontend/src/index.css index f435003..898e0fe 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,19 +1,19 @@ -html, body, #root { - width: 100%; - height: 100%; - overflow: hidden; +html, +body, +#root { + width: 100%; + height: 100%; + overflow: hidden; } body { - margin: 0; - padding: 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; + margin: 0; + padding: 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; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } diff --git a/frontend/src/index.js b/frontend/src/index.js deleted file mode 100644 index 03727e5..0000000 --- a/frontend/src/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; -import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles'; -import './index.css'; -import App from './App'; -import * as serviceWorker from './serviceWorker'; - -import theme from '@iobroker/adapter-react-v5/Theme'; -import Utils from '@iobroker/adapter-react-v5/Components/Utils'; -let themeName = Utils.getThemeName(); - -function build() { - const container = document.getElementById('root'); - const root = createRoot(container); - return root.render( - - - { - themeName = _theme; - build(); - }} - /> - - - ); -} - -build(); - -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: http://bit.ly/CRA-PWA -serviceWorker.unregister(); diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx new file mode 100644 index 0000000..56a4c63 --- /dev/null +++ b/frontend/src/index.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +const container = document.getElementById('root'); +const root = createRoot(container); +root.render(); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: http://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/frontend/src/serviceWorker.js b/frontend/src/serviceWorker.js index 012c322..86d6b25 100644 --- a/frontend/src/serviceWorker.js +++ b/frontend/src/serviceWorker.js @@ -11,121 +11,114 @@ // opt-in, read http://bit.ly/CRA-PWA. const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/), ); export function register(config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit http://bit.ly/CRA-PWA' - ); + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit http://bit.ly/CRA-PWA', + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } + } } function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' - ); + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.', + ); - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); } function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - if ( - response.status === 404 || - response.headers.get('content-type').indexOf('javascript') === -1 - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if (response.status === 404 || response.headers.get('content-type').indexOf('javascript') === -1) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log('No internet connection found. App is running in offline mode.'); }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); } export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); - } + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } } diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..9596389 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,36 @@ +// Specialized tsconfig for the admin directory, +// includes DOM typings and configures the admin build +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node", + // check JS files + "allowJs": true, + // This is necessary for the automatic typing of the adapter config + "resolveJsonModule": true, + // If you want to disable the stricter type checks (not recommended), uncomment the following line + // "strict": false, + // And enable some of those features for more fine-grained control + "strictNullChecks": true, + // "strictPropertyInitialization": true, + // "strictBindCallApply": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + "useUnknownInCatchVariables": false, + "target": "ES2022", + "baseUrl": "./", + "allowSyntheticDefaultImports": true, + "checkJs": false, + "noEmit": false, + "outDir": "./build", + "sourceMap": true, + "sourceRoot": "./src", + "noImplicitAny": true, + "skipLibCheck": true, + "verbatimModuleSyntax": true, + "lib": ["es2018", "DOM"], + "jsx": "react" + }, + "include": ["./src/**/*.ts", "./src/**/*.d.ts", "./src/**/*.tsx", "./src/**/*.json", "./src/**/*.css"], + "exclude": ["**/node_modules", "**/dist"] +} diff --git a/frontend/vite.config.mjs b/frontend/vite.config.mjs new file mode 100644 index 0000000..0aebad3 --- /dev/null +++ b/frontend/vite.config.mjs @@ -0,0 +1,36 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig(() => { + return { + build: { + outDir: 'build', + }, + plugins: [react()], + resolve: { + mainFields: [], + }, + base: './', + server: { + port: 3000, + proxy: { + '/adapter': { + target: 'http://localhost:8081', + changeOrigin: true, + secure: false, + configure: (proxy, _options) => { + proxy.on('error', (err, _req, _res) => { + console.log('proxy error', err); + }); + proxy.on('proxyReq', (proxyReq, req, _res) => { + console.log('Sending Request to the Target:', req.method, req.url); + }); + proxy.on('proxyRes', (proxyRes, req, _res) => { + console.log('Received Response from the Target:', proxyRes.statusCode, req.url); + }); + }, + }, + }, + }, + }; +}); diff --git a/package.json b/package.json index ce32cb5..0f4da76 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,38 @@ { - "version": "3.2.3", - "name": "@iobroker/repochecker", - "dependencies": { - "axios": "^1.7.9", - "compare-versions": "^6.1.1", - "image-size": "^1.2.0", - "unzipper": "^0.12.3", - "json5": "^2.2.3" - }, - "publishConfig": { - "access": "public" - }, - "devDependencies": { - "@alcalzone/release-script": "^3.8.0", - "eslint": "^8.57.0" - }, - "files": [ - "lib/", - "LICENSE", - "index.js", - "doc/issues.json" - ], - "engines": { - "node": ">=16" - }, - "main": "index.js", - "bin": "index.js", - "scripts": { - "prepublishOnly": "node doc/readme.js", - "start": "node index.js", - "lint": "eslint", - "release": "release-script", - "release-patch": "release-script patch --yes --no-update-lockfile", - "release-minor": "release-script minor --yes --no-update-lockfile", - "release-major": "release-script major --yes --no-update-lockfile" - } + "version": "3.2.3", + "name": "@iobroker/repochecker", + "dependencies": { + "axios": "^1.7.9", + "compare-versions": "^6.1.1", + "image-size": "^1.2.0", + "unzipper": "^0.12.3", + "json5": "^2.2.3" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@alcalzone/release-script": "^3.8.0", + "@iobroker/eslint-config": "^1.0.0" + }, + "files": [ + "lib/", + "LICENSE", + "index.js", + "doc/issues.json" + ], + "engines": { + "node": ">=16" + }, + "main": "index.js", + "bin": "index.js", + "scripts": { + "prepublishOnly": "node doc/readme.js", + "start": "node index.js", + "lint": "eslint", + "release": "release-script", + "release-patch": "release-script patch --yes --no-update-lockfile", + "release-minor": "release-script minor --yes --no-update-lockfile", + "release-major": "release-script major --yes --no-update-lockfile" + } } diff --git a/prettier.config.mjs b/prettier.config.mjs new file mode 100644 index 0000000..2f00708 --- /dev/null +++ b/prettier.config.mjs @@ -0,0 +1,3 @@ +import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs'; + +export default prettierConfig;