From 25d54a5c5de02a2bbbbbf3b222be9f0630570a6b Mon Sep 17 00:00:00 2001 From: muhamedsalih-tw <104364298+muhamedsalih-tw@users.noreply.github.com> Date: Mon, 31 Oct 2022 15:30:24 +0530 Subject: Convert web controls & screen to typescript (#722) --- src/components/layout/Sidebar.tsx | 2 +- src/features/webControls/components/WebControls.js | 241 -------------------- .../webControls/components/WebControls.tsx | 242 +++++++++++++++++++++ .../webControls/containers/WebControlsScreen.jsx | 151 ------------- .../webControls/containers/WebControlsScreen.tsx | 163 ++++++++++++++ 5 files changed, 406 insertions(+), 393 deletions(-) delete mode 100644 src/features/webControls/components/WebControls.js create mode 100644 src/features/webControls/components/WebControls.tsx delete mode 100644 src/features/webControls/containers/WebControlsScreen.jsx create mode 100644 src/features/webControls/containers/WebControlsScreen.tsx (limited to 'src') diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index c37447357..4eb18485f 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -111,8 +111,8 @@ interface IState { tooltipEnabled: boolean; } -@observer @inject('stores', 'actions') +@observer class Sidebar extends Component { constructor(props) { super(props); diff --git a/src/features/webControls/components/WebControls.js b/src/features/webControls/components/WebControls.js deleted file mode 100644 index 570f2f2dc..000000000 --- a/src/features/webControls/components/WebControls.js +++ /dev/null @@ -1,241 +0,0 @@ -import { createRef, Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer } from 'mobx-react'; -import injectSheet from 'react-jss'; -import { defineMessages, injectIntl } from 'react-intl'; - -import { - mdiReload, - mdiArrowRight, - mdiArrowLeft, - mdiHomeOutline, - mdiEarth, -} from '@mdi/js'; - -import Icon from '../../../components/ui/icon'; - -const messages = defineMessages({ - goHome: { - id: 'webControls.goHome', - defaultMessage: 'Home', - }, - openInBrowser: { - id: 'webControls.openInBrowser', - defaultMessage: 'Open in Browser', - }, - back: { - id: 'webControls.back', - defaultMessage: 'Back', - }, - forward: { - id: 'webControls.forward', - defaultMessage: 'Forward', - }, - reload: { - id: 'webControls.reload', - defaultMessage: 'Reload', - }, -}); - -let buttonTransition = 'none'; - -if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { - buttonTransition = 'opacity 0.25s'; -} - -const styles = theme => ({ - root: { - background: theme.colorBackground, - position: 'relative', - borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor], - zIndex: 300, - height: 50, - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - padding: [0, 10], - - '& + div': { - height: 'calc(100% - 50px)', - }, - }, - button: { - width: 30, - height: 50, - transition: buttonTransition, - - '&:hover': { - opacity: 0.8, - }, - - '&:disabled': { - opacity: 0.5, - }, - }, - icon: { - width: '20px !important', - height: 20, - marginTop: 5, - color: theme.colorText, - }, - input: { - marginBottom: 0, - height: 'auto', - margin: [0, 10], - flex: 1, - border: 0, - padding: [4, 10], - borderRadius: theme.borderRadius, - background: theme.inputBackground, - color: theme.inputColor, - }, - inputButton: { - color: theme.colorText, - }, -}); - -class WebControls extends Component { - static propTypes = { - classes: PropTypes.object.isRequired, - goHome: PropTypes.func.isRequired, - canGoBack: PropTypes.bool.isRequired, - goBack: PropTypes.func.isRequired, - canGoForward: PropTypes.bool.isRequired, - goForward: PropTypes.func.isRequired, - reload: PropTypes.func.isRequired, - openInBrowser: PropTypes.func.isRequired, - url: PropTypes.string.isRequired, - navigate: PropTypes.func.isRequired, - }; - - static getDerivedStateFromProps(props, state) { - const { url } = props; - const { editUrl } = state; - - if (!editUrl) { - return { - inputUrl: url, - editUrl: state.editUrl, - }; - } - } - - inputRef = createRef(); - - state = { - inputUrl: '', - editUrl: false, - }; - - render() { - const { - classes, - goHome, - canGoBack, - goBack, - canGoForward, - goForward, - reload, - openInBrowser, - url, - navigate, - } = this.props; - - const { inputUrl, editUrl } = this.state; - - const { intl } = this.props; - - return ( -
- - - - - - this.setState({ - inputUrl: event.target.value, - }) - } - onFocus={event => { - event.target.select(); - this.setState({ - editUrl: true, - }); - }} - onBlur={event => { - event.target.blur(); - this.setState({ - editUrl: false, - }); - }} - onKeyDown={event => { - if (event.key === 'Enter') { - this.setState({ - editUrl: false, - }); - navigate(inputUrl); - this.inputRef.current.blur(); - } else if (event.key === 'Escape') { - this.setState({ - editUrl: false, - inputUrl: url, - }); - this.inputRef.current.blur(); - } - }} - ref={this.inputRef} - /> - -
- ); - } -} - -export default injectIntl( - injectSheet(styles, { injectTheme: true })(observer(WebControls)), -); diff --git a/src/features/webControls/components/WebControls.tsx b/src/features/webControls/components/WebControls.tsx new file mode 100644 index 000000000..ebcd93c9e --- /dev/null +++ b/src/features/webControls/components/WebControls.tsx @@ -0,0 +1,242 @@ +import { createRef, Component, ReactElement, RefObject } from 'react'; +import { observer } from 'mobx-react'; +import withStyles, { WithStylesProps } from 'react-jss'; +import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import { + mdiReload, + mdiArrowRight, + mdiArrowLeft, + mdiHomeOutline, + mdiEarth, +} from '@mdi/js'; +import Icon from '../../../components/ui/icon'; + +const messages = defineMessages({ + goHome: { + id: 'webControls.goHome', + defaultMessage: 'Home', + }, + openInBrowser: { + id: 'webControls.openInBrowser', + defaultMessage: 'Open in Browser', + }, + back: { + id: 'webControls.back', + defaultMessage: 'Back', + }, + forward: { + id: 'webControls.forward', + defaultMessage: 'Forward', + }, + reload: { + id: 'webControls.reload', + defaultMessage: 'Reload', + }, +}); + +const buttonTransition = + window && window.matchMedia('(prefers-reduced-motion: no-preference)') + ? 'opacity 0.25s' + : 'none'; + +const styles = theme => ({ + root: { + background: theme.colorBackground, + position: 'relative', + borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor], + zIndex: 300, + height: 50, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + padding: [0, 10], + + '& + div': { + height: 'calc(100% - 50px)', + }, + }, + button: { + width: 30, + height: 50, + transition: buttonTransition, + + '&:hover': { + opacity: 0.8, + }, + + '&:disabled': { + opacity: 0.5, + }, + }, + icon: { + width: '20px !important', + height: 20, + marginTop: 5, + color: theme.colorText, + }, + input: { + marginBottom: 0, + height: 'auto', + margin: [0, 10], + flex: 1, + border: 0, + padding: [4, 10], + borderRadius: theme.borderRadius, + background: theme.inputBackground, + color: theme.inputColor, + }, + inputButton: { + color: theme.colorText, + }, +}); + +interface IProps extends WithStylesProps, WrappedComponentProps { + goHome: () => void; + canGoBack: boolean; + goBack: () => void; + canGoForward: boolean; + goForward: () => void; + reload: () => void; + openInBrowser: () => void; + url: string; + navigate: (url: string) => void; +} + +interface IState { + inputUrl: string; + editUrl: boolean; +} + +@observer +class WebControls extends Component { + inputRef: RefObject = createRef(); + + static getDerivedStateFromProps(props, state): IState | null { + const { url: inputUrl } = props; + const { editUrl } = state; + + return !editUrl ? { inputUrl, editUrl } : null; + } + + constructor(props: IProps) { + super(props); + + this.state = { + inputUrl: '', + editUrl: false, + }; + } + + render(): ReactElement { + const { + classes, + goHome, + canGoBack, + goBack, + canGoForward, + goForward, + reload, + openInBrowser, + url, + navigate, + intl, + } = this.props; + + const { inputUrl, editUrl } = this.state; + + return ( +
+ + + + + + this.setState({ + inputUrl: event.target.value, + }) + } + onFocus={event => { + event.target.select(); + this.setState({ + editUrl: true, + }); + }} + onBlur={event => { + event.target.blur(); + this.setState({ + editUrl: false, + }); + }} + onKeyDown={event => { + if (event.key === 'Enter') { + this.setState({ + editUrl: false, + }); + navigate(inputUrl); + } else if (event.key === 'Escape') { + this.setState({ + editUrl: false, + inputUrl: url, + }); + } + + if (this.inputRef && this.inputRef.current) { + this.inputRef.current.blur(); + } + }} + ref={this.inputRef} + /> + +
+ ); + } +} + +export default injectIntl( + withStyles(styles, { injectTheme: true })(WebControls), +); diff --git a/src/features/webControls/containers/WebControlsScreen.jsx b/src/features/webControls/containers/WebControlsScreen.jsx deleted file mode 100644 index 25e14060d..000000000 --- a/src/features/webControls/containers/WebControlsScreen.jsx +++ /dev/null @@ -1,151 +0,0 @@ -import { Component } from 'react'; -import { observer, inject } from 'mobx-react'; -import PropTypes from 'prop-types'; - -import { autorun, action, makeObservable, observable } from 'mobx'; -import WebControls from '../components/WebControls'; -import ServicesStore from '../../../stores/ServicesStore'; -import Service from '../../../models/Service'; -import { SEARCH_ENGINE_URLS } from '../../../config'; -import AppStore from '../../../stores/AppStore'; - -const URL_EVENTS = [ - 'load-commit', - 'will-navigate', - 'did-navigate', - 'did-navigate-in-page', -]; - -class WebControlsScreen extends Component { - @observable url = ''; - - @observable canGoBack = false; - - @observable canGoForward = false; - - webview = null; - - autorunDisposer = null; - - @action _setUrl(value) { - this.url = value; - } - - @action _setUrlAndHistory(value) { - this._setUrl(value); - this.canGoBack = this.webview.canGoBack(); - this.canGoForward = this.webview.canGoForward(); - } - - constructor(props) { - super(props); - - makeObservable(this); - } - - componentDidMount() { - const { service } = this.props; - - this.autorunDisposer = autorun(() => { - if (service.isAttached) { - this.webview = service.webview; - this._setUrl(this.webview.getURL()); - - for (const event of URL_EVENTS) { - this.webview.addEventListener(event, e => { - if (!e.isMainFrame) return; - - this._setUrlAndHistory(e.url); - }); - } - } - }); - } - - componentWillUnmount() { - this.autorunDisposer(); - } - - goHome() { - if (!this.webview) return; - this.webview.goToIndex(0); - } - - reload() { - if (!this.webview) return; - - this.webview.reload(); - } - - goBack() { - if (!this.webview) return; - - this.webview.goBack(); - } - - goForward() { - if (!this.webview) return; - - this.webview.goForward(); - } - - navigate(newUrl) { - if (!this.webview) return; - - let url = newUrl; - - try { - url = new URL(url).toString(); - } catch { - url = - // eslint-disable-next-line no-useless-escape - /^((?!-))(xn--)?[\da-z][\d_a-z-]{0,61}[\da-z]{0,1}\.(xn--)?([\da-z\-]{1,61}|[\da-z-]{1,30}\.[a-z]{2,})$/.test( - url, - ) - ? `http://${url}` - : SEARCH_ENGINE_URLS[this.settings.app.searchEngine]({ - searchTerm: url, - }); - } - - this.webview.loadURL(url); - this._setUrl(url); - } - - openInBrowser() { - const { openExternalUrl } = this.props.actions.app; - - if (!this.webview) return; - - openExternalUrl({ url: this.url }); - } - - render() { - return ( - this.goHome()} - reload={() => this.reload()} - openInBrowser={() => this.openInBrowser()} - canGoBack={this.canGoBack} - goBack={() => this.goBack()} - canGoForward={this.canGoForward} - goForward={() => this.goForward()} - navigate={url => this.navigate(url)} - url={this.url} - /> - ); - } -} - -export default inject('stores', 'actions')(observer(WebControlsScreen)); - -WebControlsScreen.propTypes = { - service: PropTypes.instanceOf(Service).isRequired, - stores: PropTypes.shape({ - services: PropTypes.instanceOf(ServicesStore).isRequired, - }).isRequired, - actions: PropTypes.shape({ - app: PropTypes.instanceOf(AppStore).isRequired, - service: PropTypes.instanceOf(ServicesStore).isRequired, - }).isRequired, -}; diff --git a/src/features/webControls/containers/WebControlsScreen.tsx b/src/features/webControls/containers/WebControlsScreen.tsx new file mode 100644 index 000000000..f6f1cddb8 --- /dev/null +++ b/src/features/webControls/containers/WebControlsScreen.tsx @@ -0,0 +1,163 @@ +import { Component, ReactElement } from 'react'; +import { observer, inject } from 'mobx-react'; +import { + autorun, + action, + makeObservable, + observable, + IReactionDisposer, +} from 'mobx'; +import ElectronWebView from 'react-electron-web-view'; +import WebControls from '../components/WebControls'; +import Service from '../../../models/Service'; +import { SEARCH_ENGINE_URLS } from '../../../config'; +import { StoresProps } from '../../../@types/ferdium-components.types'; + +const URL_EVENTS = [ + 'load-commit', + 'will-navigate', + 'did-navigate', + 'did-navigate-in-page', +]; + +interface IProps extends Partial { + service: Service; +} + +@inject('stores', 'actions') +@observer +class WebControlsScreen extends Component { + @observable url = ''; + + @observable canGoBack = false; + + @observable canGoForward = false; + + webview: ElectronWebView | null = null; + + autorunDisposer: IReactionDisposer | null = null; + + constructor(props) { + super(props); + + makeObservable(this); + } + + componentDidMount(): void { + const { service } = this.props; + + this.autorunDisposer = autorun(() => { + if (service.isAttached) { + this.webview = service.webview; + this._setUrl(this.webview.getURL()); + + for (const event of URL_EVENTS) { + this.webview.addEventListener(event, e => { + if (!e.isMainFrame) { + return; + } + this._setUrlAndHistory(e.url); + }); + } + } + }); + } + + componentWillUnmount(): void { + if (this.autorunDisposer) { + this.autorunDisposer(); + } + } + + @action + _setUrl(value): void { + this.url = value; + } + + @action + _setUrlAndHistory(value): void { + this._setUrl(value); + this.canGoBack = this.webview.canGoBack(); + this.canGoForward = this.webview.canGoForward(); + } + + goHome(): void { + if (!this.webview) { + return; + } + this.webview.goToIndex(0); + } + + reload(): void { + if (!this.webview) { + return; + } + + this.webview.reload(); + } + + goBack(): void { + if (!this.webview) { + return; + } + + this.webview.goBack(); + } + + goForward(): void { + if (!this.webview) { + return; + } + + this.webview.goForward(); + } + + navigate(url: string): void { + if (!this.webview) { + return; + } + + try { + url = new URL(url).toString(); + } catch { + url = + /^((?!-))(xn--)?[\da-z][\d_a-z-]{0,61}[\da-z]{0,1}\.(xn--)?([\da-z-]{1,61}|[\da-z-]{1,30}\.[a-z]{2,})$/.test( + url, + ) + ? `http://${url}` + : SEARCH_ENGINE_URLS[this.props.stores!.settings.app.searchEngine]({ + searchTerm: url, + }); + } + + this.webview.loadURL(url); + this._setUrl(url); + } + + openInBrowser(): void { + const { openExternalUrl } = this.props.actions!.app; + if (!this.webview) { + return; + } + + openExternalUrl({ url: this.url }); + } + + render(): ReactElement { + return ( + this.goHome()} + reload={() => this.reload()} + openInBrowser={() => this.openInBrowser()} + canGoBack={this.canGoBack} + goBack={() => this.goBack()} + canGoForward={this.canGoForward} + goForward={() => this.goForward()} + navigate={url => this.navigate(url)} + url={this.url} + /> + ); + } +} + +export default WebControlsScreen; -- cgit v1.2.3-70-g09d2