Merge pull request #4203 from matrix-org/travis/typescript-2

Support TypeScript for React components
pull/21833/head
Travis Ralston 2020-03-11 18:36:48 -06:00 committed by GitHub
commit 50603063e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 60 additions and 23 deletions

View File

@ -40,15 +40,15 @@
"rethemendex": "res/css/rethemendex.sh", "rethemendex": "res/css/rethemendex.sh",
"clean": "rimraf lib", "clean": "rimraf lib",
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types", "build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
"build:compile": "yarn reskindex && babel -d lib --verbose --extensions \".ts,.js\" src", "build:compile": "yarn reskindex && babel -d lib --verbose --extensions \".ts,.js,.tsx\" src",
"build:types": "tsc --emitDeclarationOnly", "build:types": "tsc --emitDeclarationOnly --jsx react",
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all", "start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all",
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn start:build\" \"yarn reskindex:watch\"", "start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn start:build\" \"yarn reskindex:watch\"",
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"", "start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"lint": "yarn lint:types && yarn lint:ts && yarn lint:js && yarn lint:style", "lint": "yarn lint:types && yarn lint:ts && yarn lint:js && yarn lint:style",
"lint:js": "eslint --max-warnings 0 --ignore-path .eslintignore.errorfiles src test", "lint:js": "eslint --max-warnings 0 --ignore-path .eslintignore.errorfiles src test",
"lint:ts": "tslint --project ./tsconfig.json -t stylish", "lint:ts": "tslint --project ./tsconfig.json -t stylish",
"lint:types": "tsc --noEmit", "lint:types": "tsc --noEmit --jsx react",
"lint:style": "stylelint 'res/css/**/*.scss'", "lint:style": "stylelint 'res/css/**/*.scss'",
"test": "jest", "test": "jest",
"test:e2e": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080" "test:e2e": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080"
@ -118,6 +118,7 @@
"@babel/preset-typescript": "^7.7.4", "@babel/preset-typescript": "^7.7.4",
"@babel/register": "^7.7.4", "@babel/register": "^7.7.4",
"@peculiar/webcrypto": "^1.0.22", "@peculiar/webcrypto": "^1.0.22",
"@types/react": "16.9",
"babel-eslint": "^10.0.3", "babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0", "babel-jest": "^24.9.0",
"chokidar": "^3.3.1", "chokidar": "^3.3.1",

View File

@ -237,7 +237,7 @@ const walkOpts = {
const fullPath = path.join(root, fileStats.name); const fullPath = path.join(root, fileStats.name);
let trs; let trs;
if (fileStats.name.endsWith('.js')) { if (fileStats.name.endsWith('.js') || fileStats.name.endsWith('.tsx')) {
trs = getTranslationsJs(fullPath); trs = getTranslationsJs(fullPath);
} else if (fileStats.name.endsWith('.html')) { } else if (fileStats.name.endsWith('.html')) {
trs = getTranslationsOther(fullPath); trs = getTranslationsOther(fullPath);

View File

@ -8,11 +8,14 @@ var chokidar = require('chokidar');
var componentIndex = path.join('src', 'component-index.js'); var componentIndex = path.join('src', 'component-index.js');
var componentIndexTmp = componentIndex+".tmp"; var componentIndexTmp = componentIndex+".tmp";
var componentsDir = path.join('src', 'components'); var componentsDir = path.join('src', 'components');
var componentGlob = '**/*.js'; var componentJsGlob = '**/*.js';
var componentTsGlob = '**/*.tsx';
var prevFiles = []; var prevFiles = [];
function reskindex() { function reskindex() {
var files = glob.sync(componentGlob, {cwd: componentsDir}).sort(); var jsFiles = glob.sync(componentJsGlob, {cwd: componentsDir}).sort();
var tsFiles = glob.sync(componentTsGlob, {cwd: componentsDir}).sort();
var files = [...tsFiles, ...jsFiles];
if (!filesHaveChanged(files, prevFiles)) { if (!filesHaveChanged(files, prevFiles)) {
return; return;
} }
@ -36,7 +39,7 @@ function reskindex() {
strm.write("let components = {};\n"); strm.write("let components = {};\n");
for (var i = 0; i < files.length; ++i) { for (var i = 0; i < files.length; ++i) {
var file = files[i].replace('.js', ''); var file = files[i].replace('.js', '').replace('.tsx', '');
var moduleName = (file.replace(/\//g, '.')); var moduleName = (file.replace(/\//g, '.'));
var importName = moduleName.replace(/\./g, "$"); var importName = moduleName.replace(/\./g, "$");
@ -79,7 +82,7 @@ if (!args.w) {
} }
var watchDebouncer = null; var watchDebouncer = null;
chokidar.watch(path.join(componentsDir, componentGlob)).on('all', (event, path) => { chokidar.watch(path.join(componentsDir, componentJsGlob)).on('all', (event, path) => {
if (path === componentIndex) return; if (path === componentIndex) return;
if (watchDebouncer) clearTimeout(watchDebouncer); if (watchDebouncer) clearTimeout(watchDebouncer);
watchDebouncer = setTimeout(reskindex, 1000); watchDebouncer = setTimeout(reskindex, 1000);

View File

@ -1,7 +1,7 @@
/* /*
Copyright 2017 Travis Ralston Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C. Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -18,41 +18,54 @@ limitations under the License.
import * as React from "react"; import * as React from "react";
import {_t} from '../../languageHandler'; import {_t} from '../../languageHandler';
import PropTypes from "prop-types"; import * as PropTypes from "prop-types";
import * as sdk from "../../index"; import * as sdk from "../../index";
import { ReactNode } from "react";
/** /**
* Represents a tab for the TabbedView. * Represents a tab for the TabbedView.
*/ */
export class Tab { export class Tab {
public label: string;
public icon: string;
public body: React.ReactNode;
/** /**
* Creates a new tab. * Creates a new tab.
* @param {string} tabLabel The untranslated tab label. * @param {string} tabLabel The untranslated tab label.
* @param {string} tabIconClass The class for the tab icon. This should be a simple mask. * @param {string} tabIconClass The class for the tab icon. This should be a simple mask.
* @param {string} tabJsx The JSX for the tab container. * @param {React.ReactNode} tabJsx The JSX for the tab container.
*/ */
constructor(tabLabel, tabIconClass, tabJsx) { constructor(tabLabel: string, tabIconClass: string, tabJsx: React.ReactNode) {
this.label = tabLabel; this.label = tabLabel;
this.icon = tabIconClass; this.icon = tabIconClass;
this.body = tabJsx; this.body = tabJsx;
} }
} }
export default class TabbedView extends React.Component { interface IProps {
tabs: Tab[];
}
interface IState {
activeTabIndex: number;
}
export default class TabbedView extends React.Component<IProps, IState> {
static propTypes = { static propTypes = {
// The tabs to show // The tabs to show
tabs: PropTypes.arrayOf(PropTypes.instanceOf(Tab)).isRequired, tabs: PropTypes.arrayOf(PropTypes.instanceOf(Tab)).isRequired,
}; };
constructor() { constructor(props: IProps) {
super(); super(props);
this.state = { this.state = {
activeTabIndex: 0, activeTabIndex: 0,
}; };
} }
_getActiveTabIndex() { private _getActiveTabIndex() {
if (!this.state || !this.state.activeTabIndex) return 0; if (!this.state || !this.state.activeTabIndex) return 0;
return this.state.activeTabIndex; return this.state.activeTabIndex;
} }
@ -62,7 +75,7 @@ export default class TabbedView extends React.Component {
* @param {Tab} tab the tab to show * @param {Tab} tab the tab to show
* @private * @private
*/ */
_setActiveTab(tab) { private _setActiveTab(tab: Tab) {
const idx = this.props.tabs.indexOf(tab); const idx = this.props.tabs.indexOf(tab);
if (idx !== -1) { if (idx !== -1) {
this.setState({activeTabIndex: idx}); this.setState({activeTabIndex: idx});
@ -71,7 +84,7 @@ export default class TabbedView extends React.Component {
} }
} }
_renderTabLabel(tab) { private _renderTabLabel(tab: Tab) {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let classes = "mx_TabbedView_tabLabel "; let classes = "mx_TabbedView_tabLabel ";
@ -97,7 +110,7 @@ export default class TabbedView extends React.Component {
); );
} }
_renderTabPanel(tab) { private _renderTabPanel(tab: Tab): React.ReactNode {
return ( return (
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}> <div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
<div className='mx_TabbedView_tabPanelContent'> <div className='mx_TabbedView_tabPanelContent'>
@ -107,7 +120,7 @@ export default class TabbedView extends React.Component {
); );
} }
render() { public render(): React.ReactNode {
const labels = this.props.tabs.map(tab => this._renderTabLabel(tab)); const labels = this.props.tabs.map(tab => this._renderTabLabel(tab));
const panel = this._renderTabPanel(this.props.tabs[this._getActiveTabIndex()]); const panel = this._renderTabPanel(this.props.tabs[this._getActiveTabIndex()]);

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import * as React from 'react';
import * as sdk from '../index'; import * as sdk from '../index';
/** /**

View File

@ -10,10 +10,12 @@
"outDir": "./lib", "outDir": "./lib",
"declaration": true, "declaration": true,
"types": [ "types": [
"node" "node",
"react"
] ]
}, },
"include": [ "include": [
"./src/**/*.ts" "./src/**/*.ts",
"./src/**/*.tsx"
] ]
} }

View File

@ -1217,6 +1217,19 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.6.tgz#cb734a7c191472ae6a2b3a502b4dfffcea974113" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.6.tgz#cb734a7c191472ae6a2b3a502b4dfffcea974113"
integrity sha512-eyK7MWD0R1HqVTp+PtwRgFeIsemzuj4gBFSQxfPHY5iMjS7474e5wq+VFgTcdpyHeNxyKSaetYAjdMLJlKoWqA== integrity sha512-eyK7MWD0R1HqVTp+PtwRgFeIsemzuj4gBFSQxfPHY5iMjS7474e5wq+VFgTcdpyHeNxyKSaetYAjdMLJlKoWqA==
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/react@16.9":
version "16.9.23"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.23.tgz#1a66c6d468ba11a8943ad958a8cb3e737568271c"
integrity sha512-SsGVT4E7L2wLN3tPYLiF20hmZTPGuzaayVunfgXzUn1x4uHVsKH6QDJQ/TdpHqwsTLd4CwrmQ2vOgxN7gE24gw==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/stack-utils@^1.0.1": "@types/stack-utils@^1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
@ -2664,6 +2677,11 @@ cssstyle@^1.0.0:
dependencies: dependencies:
cssom "0.3.x" cssom "0.3.x"
csstype@^2.2.0:
version "2.6.9"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
currently-unhandled@^0.4.1: currently-unhandled@^0.4.1:
version "0.4.1" version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"