diff --git a/README.md b/README.md index 694099aee..29be00fe9 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,8 @@ The icons may not be reused in other projects without the proper flaticon licens ### **WORK IN PROGRESS** * (foxriver76) harmonized data on csv export with actual data shown by admin +* (foxriver76) on expert mode installation tab, on no selection deactivate install button +* (foxriver76) on npm install, ensure latest version is installed ## Changelog ### 6.10.1 (2023-09-11) diff --git a/src/src/components/Object/ObjectHistoryData.tsx b/src/src/components/Object/ObjectHistoryData.tsx index 624dd236b..cebdd8091 100644 --- a/src/src/components/Object/ObjectHistoryData.tsx +++ b/src/src/components/Object/ObjectHistoryData.tsx @@ -55,7 +55,7 @@ function padding3(ms: number) { return ms; } -const styles = (theme: any) => ({ +const styles = (theme: Record) => ({ paper: { height: '100%', maxHeight: '100%', diff --git a/src/src/dialogs/GitHubInstallDialog.jsx b/src/src/dialogs/GitHubInstallDialog.jsx deleted file mode 100644 index 1ea6f6653..000000000 --- a/src/src/dialogs/GitHubInstallDialog.jsx +++ /dev/null @@ -1,490 +0,0 @@ -import React, { useCallback, useState } from 'react'; - -import PropTypes from 'prop-types'; - -import Button from '@mui/material/Button'; -import Dialog from '@mui/material/Dialog'; -import DialogActions from '@mui/material/DialogActions'; -import DialogContent from '@mui/material/DialogContent'; -import Typography from '@mui/material/Typography'; -import Paper from '@mui/material/Paper'; -import { - AppBar, - Box, - Checkbox, - FormControlLabel, - IconButton, - InputAdornment, - Tab, - Tabs, - TextField, - Autocomplete, -} from '@mui/material'; -import { makeStyles } from '@mui/styles'; - -import { FaGithub as GithubIcon } from 'react-icons/fa'; -import UrlIcon from '@mui/icons-material/Language'; -import SmsIcon from '@mui/icons-material/Sms'; -import CloseIcon from '@mui/icons-material/Close'; -import CheckIcon from '@mui/icons-material/Check'; - -import { I18n, Icon } from '@iobroker/adapter-react-v5'; - -import npmIcon from '../assets/npm.png'; - -function TabPanel(props) { - const { - children, value, index, ...other - } = props; - - return ; -} - -TabPanel.propTypes = { - children: PropTypes.node, - index: PropTypes.any.isRequired, - value: PropTypes.any.isRequired, -}; - -function a11yProps(index) { - return { - id: `full-width-tab-${index}`, - 'aria-controls': `full-width-tabpanel-${index}`, - }; -} - -const useStyles = makeStyles(theme => ({ - root: { - backgroundColor: theme.palette.background.paper, - width: '100%', - height: '100%', - }, - paper: { - maxWidth: 1000, - }, - tabPaper: { - padding: theme.spacing(2), - }, - title: { - marginTop: 10, - padding: theme.spacing(1), - marginLeft: theme.spacing(1), - fontSize: 18, - color: theme.palette.primary.main, - }, - warningText: { - color: '#f53939', - }, - noteText: { - marginTop: theme.spacing(2), - }, - errorTextNoGit: { - fontSize: 13, - color: '#ff1616', - }, - listIcon: { - width: 24, - height: 24, - }, - listIconWithMargin: { - width: 24, - height: 24, - marginRight: 8, - }, - tabSelected: { - color: theme.palette.mode === 'dark' ? theme.palette.secondary.contrastText : '#FFFFFF !important', - }, -})); - -// some older browsers do not have `flat` -if (!Array.prototype.flat) { - // eslint-disable-next-line - Object.defineProperty(Array.prototype, 'flat', { - configurable: true, - value: function flat() { - // eslint-disable-next-line - const depth = Number.isNaN(arguments[0]) ? 1 : Number(arguments[0]); - - return depth ? Array.prototype.reduce.call(this, (acc, cur) => { - if (Array.isArray(cur)) { - // eslint-disable-next-line prefer-spread - acc.push.apply(acc, flat.call(cur, depth - 1)); - } else { - acc.push(cur); - } - - return acc; - }, []) : Array.prototype.slice.call(this); - }, - writable: true, - }); -} - -const GitHubInstallDialog = ({ - categories, repository, onClose, installFromUrl, t, -}) => { - t = t || I18n.t; - - const classes = useStyles(); - const [autocompleteValue, setAutocompleteValue] = useState((window._localStorage || window.localStorage).getItem('App.autocomplete') || null); - const [debug, setDebug] = useState((window._localStorage || window.localStorage).getItem('App.gitDebug') === 'true'); - const [url, setUrl] = useState((window._localStorage || window.localStorage).getItem('App.userUrl') || ''); - const [currentTab, setCurrentTab] = useState((window._localStorage || window.localStorage).getItem('App.gitTab') || 'npm'); - - // eslint-disable-next-line array-callback-return - const list = useCallback(() => { - const adapters = categories - .map(category => category.adapters) - .flat() - .sort(); - - return adapters - .map((el, i) => { - if (i && adapters[i - 1] === el) { - return null; - } - const adapter = repository[el]; - if (!adapter?.controller) { - const parts = (adapter.extIcon || adapter.meta || adapter.readme || '').toString().split('/'); - - let name = adapter?.name; - if (!name) { - name = adapter.titleLang; - if (name && typeof name === 'object') { - name = name[I18n.getLanguage()] || name.en; - } else { - name = adapter.title || el; - } - } - - return { - value: `${el}/${parts[3]}`, - name: `${name} [${parts[3]}]`, - icon: adapter.extIcon || adapter.icon, - nogit: !!adapter.nogit, - title: el, - }; - } - return null; - }) - .filter(it => it) - .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); - }, [categories, repository]); - - const closeInit = () => { - setAutocompleteValue(null); - setUrl(''); - }; - - const _list = currentTab !== 'URL' ? list() : null; - - return - -
- - { - (window._localStorage || window.localStorage).setItem('App.gitTab', newTab); - setCurrentTab(newTab); - }} - variant="fullWidth" - indicatorColor="secondary" - > - } - {...a11yProps(0)} - value="npm" - /> - } - {...a11yProps(0)} - value="GitHub" - /> - } - {...a11yProps(1)} - value="URL" - /> - - -
- {t('Install or update the adapter from %s', currentTab || 'npm')} -
- {currentTab === 'npm' ? -
- { - (window._localStorage || window.localStorage).setItem('App.gitDebug', e.target.checked ? 'true' : 'false'); - setDebug(e.target.checked); - }} - /> - } - label={t('Debug outputs')} - /> -
-
- - { - (window._localStorage || window.localStorage).setItem('App.autocomplete', newValue); - setAutocompleteValue(newValue); - }} - options={_list} - getOptionLabel={option => option.name} - renderInput={params => { - const _params = { ...params }; - _params.InputProps = _params.InputProps || {}; - _params.InputProps.startAdornment = - - ; - - return ; - }} - renderOption={(props, option) => - img': { mr: 2, flexShrink: 0 } }} - {...props} - > - - {option.name} - } - /> -
-
- {t('Warning!')} -
-
- {t('npm_warning', 'NPM', 'NPM')} -
-
- {t('github_note')} -
-
: null} - {currentTab === 'GitHub' ? -
- { - (window._localStorage || window.localStorage).setItem('App.gitDebug', e.target.checked ? 'true' : 'false'); - setDebug(e.target.checked); - }} - /> - } - label={t('Debug outputs')} - /> -
-
- - option.nogit} - renderOption={(props, option) => - img': { mr: 2, flexShrink: 0 } }} - {...props} - > - - {option.name} - {option.nogit &&
- {I18n.t('This adapter cannot be installed from git as must be built before installation.')} -
} -
} - onChange={(_, newValue) => { - (window._localStorage || window.localStorage).setItem('App.autocomplete', newValue); - setAutocompleteValue(newValue); - }} - options={_list} - getOptionLabel={option => option.name} - renderInput={params => { - const _params = { ...params }; - _params.InputProps = _params.InputProps || {}; - _params.InputProps.startAdornment = - - ; - - return ; - }} - /> -
-
- {t('Warning!')} -
-
- {t('github_warning', 'GitHub', 'GitHub')} -
-
- {t('github_note')} -
-
: null} - {currentTab === 'URL' ? -
- { - (window._localStorage || window.localStorage).setItem('App.userUrl', event.target.value); - setUrl(event.target.value); - }} - onKeyUp={event => { - if (event.keyCode === 13 && url) { - if (!url.includes('.')) { - installFromUrl(`iobroker.${url}`, debug, true); - } else { - installFromUrl(url, debug, true); - } - } - }} - InputProps={{ - endAdornment: url ? - setUrl('')} - > - - - : null, - }} - /> -
-
- { - (window._localStorage || window.localStorage).setItem('App.gitDebug', e.target.checked ? 'true' : 'false'); - setDebug(e.target.checked); - }} - /> - } - label={t('Debug outputs')} - /> -
-
- {t('Warning!')} -
-
- {t('github_warning', 'URL', 'URL')} -
-
- {t('github_note')} -
-
: null} -
-
- - - - -
; -}; - -export default GitHubInstallDialog; diff --git a/src/src/dialogs/GitHubInstallDialog.tsx b/src/src/dialogs/GitHubInstallDialog.tsx new file mode 100644 index 000000000..e14fd5710 --- /dev/null +++ b/src/src/dialogs/GitHubInstallDialog.tsx @@ -0,0 +1,500 @@ +import React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import Paper from '@mui/material/Paper'; +import { + AppBar, + Box, + Checkbox, + FormControlLabel, + IconButton, + InputAdornment, + Tab, + Tabs, + TextField, + Autocomplete, +} from '@mui/material'; +import { Styles, withStyles } from '@mui/styles'; + +import { FaGithub as GithubIcon } from 'react-icons/fa'; +import UrlIcon from '@mui/icons-material/Language'; +import SmsIcon from '@mui/icons-material/Sms'; +import CloseIcon from '@mui/icons-material/Close'; +import CheckIcon from '@mui/icons-material/Check'; + +import { I18n, Icon } from '@iobroker/adapter-react-v5'; + +// @ts-expect-error adapt tsconfig +import npmIcon from '../assets/npm.png'; + +function a11yProps(index: number): {id: string; 'aria-controls': string} { + return { + id: `full-width-tab-${index}`, + 'aria-controls': `full-width-tabpanel-${index}`, + }; +} + +const styles = ((theme: Record) => ({ + root: { + backgroundColor: theme.palette.background.paper, + width: '100%', + height: '100%', + }, + paper: { + maxWidth: 1000, + }, + tabPaper: { + padding: theme.spacing(2), + }, + title: { + marginTop: 10, + padding: theme.spacing(1), + marginLeft: theme.spacing(1), + fontSize: 18, + color: theme.palette.primary.main, + }, + warningText: { + color: '#f53939', + }, + noteText: { + marginTop: theme.spacing(2), + }, + errorTextNoGit: { + fontSize: 13, + color: '#ff1616', + }, + listIcon: { + width: 24, + height: 24, + }, + listIconWithMargin: { + width: 24, + height: 24, + marginRight: 8, + }, + tabSelected: { + color: theme.palette.mode === 'dark' ? theme.palette.secondary.contrastText : '#FFFFFF !important', + }, +} satisfies Styles)); + +// some older browsers do not have `flat` +if (!Array.prototype.flat) { + // eslint-disable-next-line + Object.defineProperty(Array.prototype, 'flat', { + configurable: true, + value: function flat() { + // eslint-disable-next-line + const depth = Number.isNaN(arguments[0]) ? 1 : Number(arguments[0]); + + return depth ? Array.prototype.reduce.call(this, (acc: any, cur) => { + if (Array.isArray(cur)) { + // @ts-expect-error fix later + // eslint-disable-next-line prefer-spread + acc.push.apply(acc, flat.call(cur, depth - 1)); + } else { + acc.push(cur); + } + + return acc; + }, []) : Array.prototype.slice.call(this); + }, + writable: true, + }); +} + +interface GitHubInstallDialogProps { + categories: Record[]; + repository: Record; + onClose: () => void; + t: typeof I18n.t; + /** Method to install adapter */ + installFromUrl: (adapter: string, debug: boolean, customUrl: boolean) => void; + classes: Record; +} + +interface AutoCompleteValue { + value: string; + nogit: boolean; + name: string; + icon: string; + title: string; +} + +interface GitHubInstallDialogState { + autoCompleteValue: AutoCompleteValue | null; + /** If debug output is desired */ + debug: boolean; + /** The selected url */ + url: string; + /** Name of the current tab */ + currentTab: string; +} + +class GitHubInstallDialog extends React.Component { + constructor(props: GitHubInstallDialogProps) { + super(props); + + this.state = { + autoCompleteValue:((window as any)._localStorage || window.localStorage).getItem('App.autocomplete') || null, + debug: ((window as any)._localStorage || window.localStorage).getItem('App.gitDebug') === 'true', + url: ((window as any)._localStorage || window.localStorage).getItem('App.userUrl') || '', + currentTab: ((window as any)._localStorage || window.localStorage).getItem('App.gitTab') || 'npm', + }; + } + + render(): React.JSX.Element { + // eslint-disable-next-line array-callback-return + const list = (() => { + const adapters = this.props.categories + .map(category => category.adapters) + .flat() + .sort(); + + return adapters + .map((el, i) => { + if (i && adapters[i - 1] === el) { + return null; + } + const adapter = this.props.repository[el]; + if (!adapter?.controller) { + const parts = (adapter.extIcon || adapter.meta || adapter.readme || '').toString().split('/'); + + let name = adapter?.name; + if (!name) { + name = adapter.titleLang; + if (name && typeof name === 'object') { + name = name[I18n.getLanguage()] || name.en; + } else { + name = adapter.title || el; + } + } + + return { + value: `${el}/${parts[3]}`, + name: `${name} [${parts[3]}]`, + icon: adapter.extIcon || adapter.icon, + nogit: !!adapter.nogit, + title: el, + }; + } + return null; + }) + .filter(it => it) + .sort((a: any, b: any) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + }); + + const closeInit = () => { + this.setState({ autoCompleteValue: null, url: '' }); + }; + + const _list = this.state.currentTab !== 'URL' ? list() : null; + + return this.props.onClose()} + open={!0} + classes={{ paper: this.props.classes.paper }} + > + +
+ + { + ((window as any)._localStorage || window.localStorage).setItem('App.gitTab', newTab); + this.setState({ currentTab: newTab }); + }} + variant="fullWidth" + indicatorColor="secondary" + > + } + {...a11yProps(0)} + value="npm" + /> + } + {...a11yProps(0)} + value="GitHub" + /> + } + {...a11yProps(1)} + value="URL" + /> + + +
+ {this.props.t('Install or update the adapter from %s', this.state.currentTab || 'npm')} +
+ {this.state.currentTab === 'npm' ? +
+ { + ((window as any)._localStorage || window.localStorage).setItem('App.gitDebug', e.target.checked ? 'true' : 'false'); + this.setState({ debug: e.target.checked }); + }} + /> + } + label={this.props.t('Debug outputs')} + /> +
+
+ + { + ((window as any)._localStorage || window.localStorage).setItem('App.autocomplete', newValue); + this.setState({ autoCompleteValue: newValue }); + }} + // @ts-expect-error check later + options={_list} + getOptionLabel={option => option?.name ?? ''} + renderInput={params => { + const _params = { ...params }; + _params.InputProps = _params.InputProps || {}; + _params.InputProps.startAdornment = + + ; + + return ; + }} + renderOption={(props, option) => + img': { mr: 2, flexShrink: 0 } }} + {...props} + > + + {option?.name ?? ''} + } + /> +
+
+ {this.props.t('Warning!')} +
+
+ {this.props.t('npm_warning', 'NPM', 'NPM')} +
+
+ {this.props.t('github_note')} +
+
: null} + {this.state.currentTab === 'GitHub' ? +
+ { + ((window as any)._localStorage || window.localStorage).setItem('App.gitDebug', e.target.checked ? 'true' : 'false'); + this.setState({ debug: e.target.checked }); + }} + /> + } + label={this.props.t('Debug outputs')} + /> +
+
+ + !!option?.nogit} + renderOption={(props, option) => + img': { mr: 2, flexShrink: 0 } }} + {...props} + > + + {option?.name ?? ''} + {option?.nogit &&
+ {I18n.t('This adapter cannot be installed from git as must be built before installation.')} +
} +
} + onChange={(_, newValue) => { + ((window as any)._localStorage || window.localStorage).setItem('App.autocomplete', newValue); + this.setState({ autoCompleteValue: newValue }); + }} + // @ts-expect-error check later + options={_list} + getOptionLabel={option => option?.name ?? ''} + renderInput={params => { + const _params = { ...params }; + _params.InputProps = _params.InputProps || {}; + _params.InputProps.startAdornment = + + ; + + return ; + }} + /> +
+
+ {this.props.t('Warning!')} +
+
+ {this.props.t('github_warning', 'GitHub', 'GitHub')} +
+
+ {this.props.t('github_note')} +
+
: null} + {this.state.currentTab === 'URL' ? +
+ { + ((window as any)._localStorage || window.localStorage).setItem('App.userUrl', event.target.value); + this.setState({ url: event.target.value }); + }} + onKeyUp={event => { + if (event.keyCode === 13 && this.state.url) { + if (!this.state.url.includes('.')) { + this.props.installFromUrl(`iobroker.${this.state.url}`, this.state.debug, true); + } else { + this.props.installFromUrl(this.state.url, this.state.debug, true); + } + } + }} + InputProps={{ + endAdornment: this.state.url ? + this.setState({ url: '' })} + > + + + : null, + }} + /> +
+
+ { + ((window as any)._localStorage || window.localStorage).setItem('App.gitDebug', e.target.checked ? 'true' : 'false'); + this.setState({ debug: e.target.checked }); + }} + /> + } + label={this.props.t('Debug outputs')} + /> +
+
+ {this.props.t('Warning!')} +
+
+ {this.props.t('github_warning', 'URL', 'URL')} +
+
+ {this.props.t('github_note')} +
+
: null} +
+
+ + + + +
; + } +} + +export default withStyles(styles)(GitHubInstallDialog);