From e8c158141d4723526e305e27a3e841d17fde91d4 Mon Sep 17 00:00:00 2001 From: Peter McNeil Date: Sun, 29 Sep 2019 09:37:50 +0100 Subject: [PATCH 01/13] Typescript conversion - Updated all packages - Added typescript to project - Changed major files to Typescript - Converted first file successfully to Typescript One major problem is the overlapping types from DOM and React-Native. DOM is required by Electron, there seems to be no way to fix this. --- .gitignore | 2 +- android/app/build.gradle | 2 +- package.json | 129 +- src/{App.js => App.tsx} | 2 +- src/Props.tsx | 18 + .../{ActionTypes.js => ActionTypes.tsx} | 0 .../{ApiFunctions.js => ApiFunctions.tsx} | 14 +- .../{ConnectAction.js => ConnectAction.tsx} | 29 +- ...ectronRedirect.js => ElectronRedirect.tsx} | 2 +- .../{EntryComponent.js => EntryComponent.tsx} | 48 +- .../{HomeComponent.js => HomeComponent.tsx} | 0 src/components/{Loading.js => Loading.tsx} | 0 .../{LoginComponent.js => LoginComponent.tsx} | 42 +- src/components/{NoMatch.js => NoMatch.tsx} | 2 +- src/components/{Style.js => Style.tsx} | 0 .../{authReducer.js => authReducer.tsx} | 0 .../{connectReducer.js => connectReducer.tsx} | 0 ...terfaceReducer.js => interfaceReducer.tsx} | 0 ...iceWorker.js => registerServiceWorker.tsx} | 0 .../{EntryScreen.js => EntryScreen.tsx} | 2 +- src/utilities/routing/{index.js => index.tsx} | 0 .../{normalStorage.js => normalStorage.tsx} | 2 +- src/utilities/storage/sensitiveStorage.js | 3 - src/utilities/storage/sensitiveStorage.tsx | 3 + src/utilities/storage/{store.js => store.ts} | 0 tsconfig.json | 36 + types/jellyfin-apiclient/index.d.ts | 1 + types/redux-persist/index.d.ts | 1 + yarn.lock | 5513 ++++++++++------- 29 files changed, 3377 insertions(+), 2474 deletions(-) rename src/{App.js => App.tsx} (95%) create mode 100644 src/Props.tsx rename src/actions/{ActionTypes.js => ActionTypes.tsx} (100%) rename src/actions/{ApiFunctions.js => ApiFunctions.tsx} (61%) rename src/actions/{ConnectAction.js => ConnectAction.tsx} (58%) rename src/components/{ElectronRedirect.js => ElectronRedirect.tsx} (82%) rename src/components/{EntryComponent.js => EntryComponent.tsx} (65%) rename src/components/{HomeComponent.js => HomeComponent.tsx} (100%) rename src/components/{Loading.js => Loading.tsx} (100%) rename src/components/{LoginComponent.js => LoginComponent.tsx} (72%) rename src/components/{NoMatch.js => NoMatch.tsx} (76%) rename src/components/{Style.js => Style.tsx} (100%) rename src/reducers/{authReducer.js => authReducer.tsx} (100%) rename src/reducers/{connectReducer.js => connectReducer.tsx} (100%) rename src/reducers/{interfaceReducer.js => interfaceReducer.tsx} (100%) rename src/{registerServiceWorker.js => registerServiceWorker.tsx} (100%) rename src/screens/{EntryScreen.js => EntryScreen.tsx} (97%) rename src/utilities/routing/{index.js => index.tsx} (100%) rename src/utilities/storage/{normalStorage.js => normalStorage.tsx} (96%) delete mode 100644 src/utilities/storage/sensitiveStorage.js create mode 100644 src/utilities/storage/sensitiveStorage.tsx rename src/utilities/storage/{store.js => store.ts} (100%) create mode 100644 tsconfig.json create mode 100644 types/jellyfin-apiclient/index.d.ts create mode 100644 types/redux-persist/index.d.ts diff --git a/.gitignore b/.gitignore index 712e532..77856fe 100644 --- a/.gitignore +++ b/.gitignore @@ -67,4 +67,4 @@ public/favicon* **/.#* # TernJS -*/.tern-port \ No newline at end of file +*/.tern-port diff --git a/android/app/build.gradle b/android/app/build.gradle index b4cc1f7..a5fd609 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -73,7 +73,7 @@ import com.android.build.OutputFile */ project.ext.react = [ - entryFile: "index.js" + entryFile: "index.tsx" ] apply from: "../../node_modules/react-native/react.gradle" diff --git a/package.json b/package.json index 07a388c..11155ec 100644 --- a/package.json +++ b/package.json @@ -13,14 +13,14 @@ "android:kill_server": "scripts/kill_server.sh", "android:log": "react-native log-android", "android:release": "cd android && ./gradlew assembleRelease", - "electron": "electron build", - "electron:release": "electron-packager build --all --asar --icon=public/app --overwrite --out=electron", + "electron": "electron types", + "electron:release": "electron-packager types --all --asar --icon=public/app --overwrite --out=electron", "icon-gen": "icon-gen -i resources/icon.svg -o public && shx cp public/favicon-228.png public/app.png", "install": "node scripts/install.js", "ios": "react-native run-ios", "ios:release": "react-native bundle --platform=ios", "preandroid": "scripts/preandroid.sh", - "preelectron": "cross-env-shell ELECTRON=1 PUBLIC_URL=. \"yarn run web:release && shx cp index.electron.js build/index.js && echo {} > build/package.json\"", + "preelectron": "cross-env-shell ELECTRON=1 PUBLIC_URL=. \\\"yarn run web:release && shx cp index.electron.js types/index.js && echo {} > build/package.json\\\"", "preelectron:release": "yarn run preelectron", "preweb:release": "yarn run icon-gen", "release": "yarn run android:release && yarn run electron:release && yarn run ios:release && yarn run web:release && yarn run windows:release", @@ -31,22 +31,25 @@ "windows:release": "react-native bundle --platform=windows --entry-file index.js --assets-dest windows/jellyfinclient/ReactAssets --bundle-output windows/jellyfinclient/ReactAssets/index.windows.bundle --dev false" }, "dependencies": { + "@types/node": "^12.7.8", + "@types/react-dom": "^16.9.1", + "awesome-typescript-loader": "^5.2.1", "electron-store": "^2.0.0", "electron-store-webpack-wrapper": "^0.0.2", - "formik": "^1.4.2", - "fs-extra": "7.0.0", + "formik": "^1.5.8", + "fs-extra": "^7.0.0", "is-electron": "^2.2.0", "jellyfin-apiclient": "^1.0.0", "prop-types": "^15.6.2", - "react": "16.6.3", + "react": "^16.10.0", "react-app-polyfill": "^0.1.3", - "react-art": "16.7.0", + "react-art": "^16.7.0", "react-dev-utils": "^6.1.1", - "react-dom": "16.7.0", - "react-native": "0.59.9", + "react-dom": "^16.10.0", + "react-native": "^0.61.1", "react-native-sensitive-info": "^5.2.7", - "react-native-web_improved": "0.9.9", - "react-native-windows": "0.57.0-rc.4", + "react-native-web_improved": "^0.9.9", + "react-native-windows": "^0.59.0-rc.3", "react-redux": "^6.0.0", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", @@ -57,68 +60,72 @@ "redux-persist-electron-storage": "^2.0.0", "redux-persist-sensitive-storage": "^1.0.0", "redux-thunk": "^2.3.0", - "resolve": "1.8.1" + "resolve": "^1.8.1", + "source-map-loader": "^0.2.4" }, "devDependencies": { "@babel/core": "^7.2.2", - "@svgr/webpack": "2.4.1", - "@types/jest": "^23.3.13", - "@types/react": "^16.7.20", - "@types/react-native": "^0.57.29", + "@svgr/webpack": "^2.4.1", + "@types/jest": "^24.0.18", + "@types/react": "^16.9.3", + "@types/react-native": "^0.60.17", "@types/react-redux": "^7.0.0", - "babel-core": "7.0.0-bridge.0", - "babel-eslint": "9.0.0", - "babel-jest": "23.6.0", - "babel-loader": "8.0.4", + "@types/react-test-renderer": "^16.9.0", + "babel-core": "^7.0.0-bridge.0", + "babel-eslint": "^9.0.0", + "babel-jest": "^23.6.0", + "babel-loader": "^8.0.4", "babel-plugin-named-asset-import": "^0.2.3", - "babel-plugin-react-native-web": "0.9.9", - "babel-plugin-transform-react-remove-prop-types": "0.4.21", + "babel-plugin-react-native-web": "^0.9.9", + "babel-plugin-transform-react-remove-prop-types": "^0.4.21", "babel-preset-react-app": "^6.1.0", - "bfj": "6.1.1", - "case-sensitive-paths-webpack-plugin": "2.1.2", - "chalk": "2.4.1", + "bfj": "^6.1.1", + "case-sensitive-paths-webpack-plugin": "^2.1.2", + "chalk": "^2.4.1", "cross-env": "^5.2.0", - "css-loader": "1.0.0", - "dotenv": "6.0.0", - "dotenv-expand": "4.2.0", + "css-loader": "^1.0.0", + "dotenv": "^6.0.0", + "dotenv-expand": "^4.2.0", "electron": "^4.0.1", "electron-packager": "^13.0.1", - "eslint": "5.6.0", + "eslint": "^5.16.0", "eslint-config-react-app": "^3.0.5", - "eslint-loader": "2.1.1", - "eslint-plugin-flowtype": "2.50.1", - "eslint-plugin-import": "2.14.0", - "eslint-plugin-jsx-a11y": "6.1.2", - "eslint-plugin-react": "7.11.1", - "file-loader": "2.0.0", - "fork-ts-checker-webpack-plugin-alt": "0.4.14", - "html-webpack-plugin": "4.0.0-alpha.2", - "icon-gen": "2.0.0", - "identity-obj-proxy": "3.0.0", - "jest": "23.6.0", - "jest-pnp-resolver": "1.0.1", - "jest-resolve": "23.6.0", - "metro-react-native-babel-preset": "0.51.1", - "mini-css-extract-plugin": "0.4.3", - "optimize-css-assets-webpack-plugin": "5.0.1", - "pnp-webpack-plugin": "1.1.0", - "postcss-flexbugs-fixes": "4.1.0", - "postcss-loader": "3.0.0", - "postcss-preset-env": "6.0.6", - "postcss-safe-parser": "4.0.1", + "eslint-loader": "^2.1.1", + "eslint-plugin-flowtype": "^2.50.1", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-jsx-a11y": "^6.1.2", + "eslint-plugin-react": "^7.11.1", + "file-loader": "^2.0.0", + "fork-ts-checker-webpack-plugin-alt": "^0.4.14", + "html-webpack-plugin": "^4.0.0-alpha.2", + "icon-gen": "^2.0.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^24.9.0", + "jest-pnp-resolver": "^1.0.1", + "jest-resolve": "^23.6.0", + "metro-react-native-babel-preset": "^0.51.1", + "mini-css-extract-plugin": "^0.4.3", + "optimize-css-assets-webpack-plugin": "^5.0.1", + "pnp-webpack-plugin": "^1.1.0", + "postcss-flexbugs-fixes": "^4.1.0", + "postcss-loader": "^3.0.0", + "postcss-preset-env": "^6.0.6", + "postcss-safe-parser": "^4.0.1", "prettier": "^1.18.2", - "react-test-renderer": "16.6.3", - "rnpm-plugin-windows": "0.2.8", - "sass-loader": "7.1.0", + "react-native-typescript-transformer": "^1.2.13", + "react-test-renderer": "^16.6.3", + "rnpm-plugin-windows": "^0.2.8", + "sass-loader": "^7.1.0", "shx": "^0.3.2", - "style-loader": "0.23.0", - "terser-webpack-plugin": "1.1.0", - "typescript": "^3.2.4", - "url-loader": "1.1.1", - "webpack": "4.19.1", - "webpack-dev-server": "3.1.14", - "webpack-manifest-plugin": "2.0.4", - "workbox-webpack-plugin": "3.6.3" + "style-loader": "^0.23.0", + "terser-webpack-plugin": "^1.1.0", + "ts-jest": "^24.1.0", + "typescript": "^3.6.3", + "url-loader": "^1.1.1", + "webpack": "^4.19.1", + "webpack-dev-server": "^3.1.14", + "webpack-manifest-plugin": "^2.0.4", + "workbox-webpack-plugin": "^3.6.3" }, "jest": { "preset": "react-native", diff --git a/src/App.js b/src/App.tsx similarity index 95% rename from src/App.js rename to src/App.tsx index bd478c5..2d1d168 100644 --- a/src/App.js +++ b/src/App.tsx @@ -5,7 +5,7 @@ import EntryScreen from "./screens/EntryScreen"; import NoMatch from "./components/NoMatch"; import ElectronRedirect from "./components/ElectronRedirect"; import jellyfinStore from "./utilities/storage/store"; -import { Router, Switch, Route } from "./utilities/routing/index"; +import { Router, Switch, Route } from "./utilities/routing"; import { PersistGate } from "redux-persist/integration/react"; import Loading from "./components/Loading"; import HomeComponent from "./components/HomeComponent"; diff --git a/src/Props.tsx b/src/Props.tsx new file mode 100644 index 0000000..61b4d2c --- /dev/null +++ b/src/Props.tsx @@ -0,0 +1,18 @@ +// Contains the definition for the props used in Jellyfin +export interface Storage { + jellyfinInterface : { + apiClient: any + }, + authCredentials: any +} +export interface ConnectionStatus { + serverAddress: string, + serverPort: string, + connectStatus: boolean +} + +export interface JellyfinProps { + connectionStatus: ConnectionStatus, + connectAction: any, + storage: Storage +} diff --git a/src/actions/ActionTypes.js b/src/actions/ActionTypes.tsx similarity index 100% rename from src/actions/ActionTypes.js rename to src/actions/ActionTypes.tsx diff --git a/src/actions/ApiFunctions.js b/src/actions/ApiFunctions.tsx similarity index 61% rename from src/actions/ApiFunctions.js rename to src/actions/ApiFunctions.tsx index 156fa3e..e121e5d 100644 --- a/src/actions/ApiFunctions.js +++ b/src/actions/ApiFunctions.tsx @@ -2,17 +2,17 @@ import ApiClient from "jellyfin-apiclient/dist/apiclient"; import jellyfinStore from "../utilities/storage/store"; import * as types from "./ActionTypes"; -export const connectToJellyfin = function(address) { - let apiClient = new ApiClient(null, address, "Jellyfin WebNG", "0.0.1", "WebNG", "WebNG", ""); +export const connectToJellyfin = function(address: string) { + const apiClient = new ApiClient(null, address, "Jellyfin WebNG", "0.0.1", "WebNG", "WebNG", ""); jellyfinStore.store.dispatch({ type: types.UPDATE_APICLIENT, apiClient: apiClient }); }; -export const loginToJellyfin = async function(username, password) { - let apiClient = copyClientFromStore(); - let auth = await apiClient.authenticateUserByName(username, password); +export const loginToJellyfin = async function(username: string, password: string) { + const apiClient = copyClientFromStore(); + const auth = await apiClient.authenticateUserByName(username, password); jellyfinStore.store.dispatch(loginSuccessfully(auth.User.Name, auth.User.Id, auth.AccessToken)); apiClient.setAuthenticationInfo(auth.AccessToken, auth.User.Id); jellyfinStore.store.dispatch({ @@ -22,13 +22,13 @@ export const loginToJellyfin = async function(username, password) { }; export const copyClientFromStore = function() { - let apiClient = jellyfinStore.store.getState().jellyfinInterface.apiClient; + const apiClient = jellyfinStore.store.getState().jellyfinInterface.apiClient; return Object.assign(new ApiClient(null, "-", "Jellyfin WebNG", "0.0.1", "WebNG", "WebNG", ""), apiClient); }; //Dispatch Action Helper: -function loginSuccessfully(username, userid, token) { +function loginSuccessfully(username: string, userid: string, token: string) { return { type: types.LOGIN_SUCCESSFUL, username, diff --git a/src/actions/ConnectAction.js b/src/actions/ConnectAction.tsx similarity index 58% rename from src/actions/ConnectAction.js rename to src/actions/ConnectAction.tsx index a3d42ec..9c2da9f 100644 --- a/src/actions/ConnectAction.js +++ b/src/actions/ConnectAction.tsx @@ -2,19 +2,24 @@ import * as types from "./ActionTypes"; import { connectToJellyfin } from "./ApiFunctions"; import jellyfinStore from "../utilities/storage/store"; -export default function connectToServer(serverAddress, port) { - return dispatch => { +export default function connectToServer(serverAddress: string, port: string) { + return (dispatch: any) => { let plainServerAddress = serverAddress; serverAddress = normalizeAddress(serverAddress); serverAddress = serverAddress + ":" + port; connectToJellyfin(serverAddress); try { - let apiClient = jellyfinStore.store.getState().jellyfinInterface.apiClient; - apiClient.getPublicSystemInfo().then(result => { - console.log("Connected"); - console.log(result); - return dispatch(connectSuccessful(plainServerAddress, port)); - }); + //TODO No any anywhere + const apiClient: any = jellyfinStore.store.getState().jellyfinInterface.apiClient; + if(apiClient) { + apiClient.getPublicSystemInfo().then((result: any) => { + console.log("Connected"); + console.log(result); + return dispatch(connectSuccessful(plainServerAddress, port)); + }); + } else { + throw new Error("API Client is undefined"); + } } catch (err) { console.log("Unable to connect."); return dispatch(connectFailed(plainServerAddress, port)); @@ -22,12 +27,12 @@ export default function connectToServer(serverAddress, port) { }; } -function replaceAll(originalString, strReplace, strWith) { +function replaceAll(originalString: string, strReplace: string, strWith: string) { const reg = new RegExp(strReplace, "ig"); return originalString.replace(reg, strWith); } -function normalizeAddress(serverAddress) { +function normalizeAddress(serverAddress: string) { // attempt to correct bad input serverAddress = serverAddress.trim(); @@ -42,7 +47,7 @@ function normalizeAddress(serverAddress) { return serverAddress; } -function connectSuccessful(address, port) { +function connectSuccessful(address: string, port: string) { return { type: types.CONNECT_SUCCESSFUL, address, @@ -50,7 +55,7 @@ function connectSuccessful(address, port) { }; } -function connectFailed(address, port) { +function connectFailed(address: string, port: string) { return { type: types.CONNECT_FAILED, address, diff --git a/src/components/ElectronRedirect.js b/src/components/ElectronRedirect.tsx similarity index 82% rename from src/components/ElectronRedirect.js rename to src/components/ElectronRedirect.tsx index f53675f..0890b12 100644 --- a/src/components/ElectronRedirect.js +++ b/src/components/ElectronRedirect.tsx @@ -1,5 +1,5 @@ import React, { Component } from "react"; -import { Redirect } from "../utilities/routing/index"; +import { Redirect } from "../utilities/routing"; //This is a component to make Electron load index.html and then forward to the correct Route. diff --git a/src/components/EntryComponent.js b/src/components/EntryComponent.tsx similarity index 65% rename from src/components/EntryComponent.js rename to src/components/EntryComponent.tsx index 6ddb191..a9c1560 100644 --- a/src/components/EntryComponent.js +++ b/src/components/EntryComponent.tsx @@ -1,20 +1,29 @@ -import React, { Component } from "react"; +import React, { PureComponent } from "react"; import { View, TextInput, Button, Image } from "react-native"; import styles from "./Style"; import { Formik } from "formik"; import { connect } from "react-redux"; import { Redirect } from "../utilities/routing"; +import { ConnectionStatus, JellyfinProps } from "../Props"; -class EntryComponent extends Component { - constructor(props, context) { - super(props, context); - this.state = { - server: "", - port: "", - connectButtonMessage: "Connect", +interface EntryComponentState { + server: string, + port: string, + connectButtonMessage?: string, + connectionStatus: ConnectionStatus +} + +class EntryComponent extends PureComponent { + state = { + server: "", + port: "", + connectButtonMessage: "Connect", + connectionStatus : { + serverAddress: "", + serverPort: "", connectStatus: false - }; - } + } + }; componentDidMount() { this.setState({ @@ -24,23 +33,28 @@ class EntryComponent extends Component { } componentDidUpdate() { - if (this.state.connectStatus !== this.props.connectionStatus.connectStatus) + if (this.state.connectionStatus.connectStatus !== this.props.connectionStatus.connectStatus) this.setState({ - connectStatus: this.props.connectionStatus.connectStatus + connectionStatus: { + serverAddress: this.props.connectionStatus.serverAddress, + serverPort: this.props.connectionStatus.serverPort, + connectStatus: this.props.connectionStatus.connectStatus + } }); } render() { - return this.state.connectStatus ? ( + return this.state.connectionStatus.connectStatus ? ( ) : ( { this.props.connectAction(this.state); }} - render={({ handleSubmit }) => ( + render={props => ( @@ -65,7 +79,7 @@ class EntryComponent extends Component { -