From 83ef538a5ca385b40f4b5f98c30c44bb0bdff09f Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 11 Dec 2020 15:22:30 -0800 Subject: [PATCH] feat(ui): add react, make a small component (#158) This is a first step to convert to React. --- package-lock.json | 73 ++++++++++++++++++- package.json | 8 +- src/traceViewer/web/ui/contextSelector.css | 30 ++++++++ src/traceViewer/web/ui/contextSelector.tsx | 42 +++++++++++ src/traceViewer/web/ui/filmStripView.ts | 4 +- src/traceViewer/web/ui/workbench.css | 15 ---- .../web/ui/{workbench.ts => workbench.tsx} | 17 ++--- src/traceViewer/web/web.webpack.config.js | 4 +- tsconfig.json | 5 +- 9 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 src/traceViewer/web/ui/contextSelector.css create mode 100644 src/traceViewer/web/ui/contextSelector.tsx rename src/traceViewer/web/ui/{workbench.ts => workbench.tsx} (80%) diff --git a/package-lock.json b/package-lock.json index ae0d28e..08b23a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1214,6 +1214,31 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.7.tgz", "integrity": "sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg==" }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true + }, + "@types/react": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz", + "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.0.tgz", + "integrity": "sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -2491,6 +2516,12 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "csstype": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==", + "dev": true + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -4518,8 +4549,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "jsbn": { "version": "0.1.1", @@ -4636,6 +4666,14 @@ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "lower-case": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", @@ -5084,8 +5122,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -5883,6 +5920,25 @@ "safe-buffer": "^5.1.0" } }, + "react": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", + "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-dom": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", + "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.1" + } + }, "react-is": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", @@ -6199,6 +6255,15 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "scheduler": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz", + "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "schema-utils": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", diff --git a/package.json b/package.json index c6a7fe3..46d4bdf 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,16 @@ "dependencies": { "commander": "^6.1.0", "highlight.js": "^10.1.2", - "playwright": "=1.7.0-next.1607623793189" + "playwright": "=1.7.0-next.1607623793189", + "react": "^17.0.1", + "react-dom": "^17.0.1" }, "devDependencies": { - "folio": "^0.3.16", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", "css-loader": "^4.3.0", "file-loader": "^6.1.0", + "folio": "^0.3.16", "html-webpack-plugin": "^4.4.1", "monaco-editor": "^0.20.0", "pkg": "^4.4.9", diff --git a/src/traceViewer/web/ui/contextSelector.css b/src/traceViewer/web/ui/contextSelector.css new file mode 100644 index 0000000..7f11ada --- /dev/null +++ b/src/traceViewer/web/ui/contextSelector.css @@ -0,0 +1,30 @@ +/* + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +.context-selector { + min-width: 38px; + border: 1px solid transparent; + border-radius: 0; + padding: 0 13px 0 5px; + position: relative; + height: 22px; + align-self: center; + margin-right: 20px; + background: rgba(222, 222, 222, 0.3); + color: white; + border-radius: 3px; + outline: none !important; +} diff --git a/src/traceViewer/web/ui/contextSelector.tsx b/src/traceViewer/web/ui/contextSelector.tsx new file mode 100644 index 0000000..7996862 --- /dev/null +++ b/src/traceViewer/web/ui/contextSelector.tsx @@ -0,0 +1,42 @@ +/* + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import * as React from 'react'; +import { ContextEntry } from '../../traceModel'; +import './contextSelector.css'; + +export const ContextSelector: React.FunctionComponent<{ + contexts: ContextEntry[], + onChange: (contextEntry: ContextEntry) => void, +}> = ({ contexts, onChange }) => { + const [index, setIndex] = React.useState(0); + return ( + + ); +}; diff --git a/src/traceViewer/web/ui/filmStripView.ts b/src/traceViewer/web/ui/filmStripView.ts index 53bc363..2acd790 100644 --- a/src/traceViewer/web/ui/filmStripView.ts +++ b/src/traceViewer/web/ui/filmStripView.ts @@ -148,7 +148,9 @@ export class FilmStripView { updatePreview(clientX: number, time: number) { // TODO: pick file from the Y position. const video = this._videos[0]; - const metainfo = this._metainfo.get(video)!; + const metainfo = this._metainfo.get(video); + if (!metainfo) + return; const image = new Image(metainfo.width / 2 | 0, metainfo.height / 2 | 0); const index = (time - metainfo.startTime) / (metainfo.endTime - metainfo.startTime) * metainfo.frames | 0; diff --git a/src/traceViewer/web/ui/workbench.css b/src/traceViewer/web/ui/workbench.css index 3c6e190..0663f69 100644 --- a/src/traceViewer/web/ui/workbench.css +++ b/src/traceViewer/web/ui/workbench.css @@ -53,21 +53,6 @@ flex: auto; } -.workbench .context-selector { - min-width: 38px; - border: 1px solid transparent; - border-radius: 0; - padding: 0 13px 0 5px; - position: relative; - height: 22px; - align-self: center; - margin-right: 20px; - background: rgba(222, 222, 222, 0.3); - color: white; - border-radius: 3px; - outline: none !important; -} - tab-strip { background-color: var(--light-background); } diff --git a/src/traceViewer/web/ui/workbench.ts b/src/traceViewer/web/ui/workbench.tsx similarity index 80% rename from src/traceViewer/web/ui/workbench.ts rename to src/traceViewer/web/ui/workbench.tsx index ce860ef..2f86a7f 100644 --- a/src/traceViewer/web/ui/workbench.ts +++ b/src/traceViewer/web/ui/workbench.tsx @@ -20,27 +20,24 @@ import { ActionListView } from './actionListView'; import { PropertiesTabbedPane } from './propertiesTabbedPane'; import { TimelineView } from './timelineView'; import './workbench.css'; +import * as ReactDOM from 'react-dom'; +import * as React from 'react'; +import { ContextSelector } from './contextSelector'; export class Workbench { element: HTMLElement; private _tabbedPane: PropertiesTabbedPane | undefined; private _timelineGrid: TimelineView | undefined; - private _contextSelector: HTMLSelectElement; + private _contextSelectorDiv: HTMLElement; constructor(trace: TraceModel) { - this._contextSelector = dom`` as HTMLElement as HTMLSelectElement; - this._contextSelector.addEventListener('input', () => { - this.showContext(trace.contexts[this._contextSelector.selectedIndex]); - }); - if (trace.contexts.length === 1) - this._contextSelector.style.visibility = 'hidden'; + this._contextSelectorDiv = dom`
`; this.element = dom` `; window.addEventListener('resize', () => this.pack()); + ReactDOM.render( this.showContext(context)} />, this._contextSelectorDiv); this.showContext(trace.contexts[0]); } @@ -55,7 +52,7 @@ export class Workbench {
Playwright
- ${this._contextSelector} + ${this._contextSelectorDiv} ${this._timelineGrid.element} diff --git a/src/traceViewer/web/web.webpack.config.js b/src/traceViewer/web/web.webpack.config.js index af1345f..84459a6 100644 --- a/src/traceViewer/web/web.webpack.config.js +++ b/src/traceViewer/web/web.webpack.config.js @@ -7,7 +7,7 @@ module.exports = { app: path.join(__dirname, 'index.ts'), }, resolve: { - extensions: ['.ts', '.js'] + extensions: ['.ts', '.js', '.tsx', '.jsx'] }, output: { globalObject: 'self', @@ -17,7 +17,7 @@ module.exports = { module: { rules: [ { - test: /\.ts?$/, + test: /\.(j|t)sx?$/, use: 'ts-loader', exclude: /node_modules/ }, diff --git a/tsconfig.json b/tsconfig.json index 52b6946..e837ad0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,9 +8,10 @@ "outDir": "./lib", "strict": true, "declaration": false, - "noUnusedLocals": true + "noUnusedLocals": true, + "jsx": "react" }, "compileOnSave": true, - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": ["node_modules"] }