From 1b3dfc3ab5d42fe2c833d49f8e2e806be88c7874 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Kazuhiro Date: Wed, 20 Dec 2023 12:59:49 +0900 Subject: [PATCH 1/7] Devtools: telemetry recording --- devtools-frontend/package.json | 1 + devtools-frontend/src/components/Layout.tsx | 54 ++++- devtools-frontend/src/main.tsx | 2 + devtools-frontend/src/worker.ts | 227 ++++++++++++++++++++ devtools-frontend/tsconfig.json | 2 +- 5 files changed, 282 insertions(+), 4 deletions(-) diff --git a/devtools-frontend/package.json b/devtools-frontend/package.json index 540578e0..a84db69b 100644 --- a/devtools-frontend/package.json +++ b/devtools-frontend/package.json @@ -46,6 +46,7 @@ "@types/react-dom": "18.2.21", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", + "@types/wicg-file-system-access": "^2023.10.4", "@vitejs/plugin-react": "4.2.1", "autoprefixer": "10.4.18", "eslint": "8.57.0", diff --git a/devtools-frontend/src/components/Layout.tsx b/devtools-frontend/src/components/Layout.tsx index e3317987..ad0a7313 100644 --- a/devtools-frontend/src/components/Layout.tsx +++ b/devtools-frontend/src/components/Layout.tsx @@ -1,5 +1,5 @@ import { Classes, Icon } from "@blueprintjs/core"; -import React, { useMemo } from "react"; +import React, { useMemo, useEffect, useState } from "react"; import { Link, NavLink, @@ -13,6 +13,7 @@ import { SatelliteSchema } from "../proto/tmtc_generic_c2a"; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; import { IconNames } from "@blueprintjs/icons"; import type { GrpcClientService } from "../worker"; +import type { RecorderService, RecordingStatus } from "../recorderWorker"; type TelemetryMenuItem = { name: string; @@ -31,6 +32,42 @@ const TelemetryListSidebar: React.FC = ({ activeName: tmivName, telemetryListItems, }) => { + const recorder = (useLoaderData() as ClientContext).client; + const [recorderStatus, setRecordingStatus] = useState( + null + ); + useEffect(() => { + const readerP = recorder + .openRecordingStatusStream() + .then((stream) => stream.getReader()); + let cancel; + const cancelP = new Promise((resolve) => (cancel = resolve)); + Promise.all([readerP, cancelP]).then(([reader]) => reader.cancel()); + readerP.then(async (reader) => { + // eslint-disable-next-line no-constant-condition + while (true) { + const next = await reader.read(); + if (next.done) { + break; + } + setRecordingStatus(next.value); + } + }); + return cancel; + }, [recorder]); + const toggleRecordingStatus = async (name: string) => { + if (!recorderStatus?.directoryIsSet) { + const directoryHandle = await window.showDirectoryPicker({ + mode: "readwrite", + }); + recorder.setRootRecordDirectory(directoryHandle); + } + if (recorderStatus?.recordingTelemetries.has(name)) { + recorder.disableRecording(name); + } else { + recorder.enableRecording(name); + } + }; return (
    @@ -82,6 +119,17 @@ const TelemetryListSidebar: React.FC = ({ tmivName === item.name ? Classes.ACTIVE : "" }`} > + toggleRecordingStatus(item.name)}> + {recorderStatus?.recordingTelemetries.has(item.name) + ?
    +
    +
    + :
    +
    +
    + + } +
    @@ -108,10 +156,10 @@ export const Layout = () => { const items: TelemetryMenuItem[] = []; const channelNames = Object.keys(ctx.satelliteSchema.telemetryChannels); for (const [componentName, componentSchema] of Object.entries( - ctx.satelliteSchema.telemetryComponents, + ctx.satelliteSchema.telemetryComponents )) { for (const [telemetryName, telemetrySchema] of Object.entries( - componentSchema.telemetries, + componentSchema.telemetries )) { for (const channelName of channelNames) { const name = `${channelName}.${componentName}.${telemetryName}`; diff --git a/devtools-frontend/src/main.tsx b/devtools-frontend/src/main.tsx index 256d2afd..d49d3d14 100644 --- a/devtools-frontend/src/main.tsx +++ b/devtools-frontend/src/main.tsx @@ -16,6 +16,7 @@ import { CommandView } from "./components/CommandView"; import { OldCommandView } from "./components/OldCommandView"; import { buildClient } from "./client"; import type { GrpcClientService } from "./worker"; +import type { RecorderService } from "./recorderWorker"; import { IconNames } from "@blueprintjs/icons"; import { FriendlyError } from "./error"; @@ -36,6 +37,7 @@ const clientLoader: LoaderFunction = async () => { details: "Make sure that your tmtc-c2a is running.", }); })!; + return { client, satelliteSchema }; }; diff --git a/devtools-frontend/src/worker.ts b/devtools-frontend/src/worker.ts index 4139536e..190620d9 100644 --- a/devtools-frontend/src/worker.ts +++ b/devtools-frontend/src/worker.ts @@ -18,6 +18,11 @@ export type GrpcClientService = { postCommand(input: PostCommandRequest): Promise; openTelemetryStream(tmivName: string): Promise>; lastTelemetryValue(tmivName: string): Promise; + setRootRecordDirectory(directory: FileSystemDirectoryHandle): Promise; + hasRecordDirectory(): Promise; + enableRecording(telemetryName: string): Promise; + disableRecording(telemetryName: string): Promise; + openRecordingStatusStream(): Promise>; }; export type WorkerRpcService = { @@ -63,6 +68,175 @@ const startTelemetryStream = async () => { } }; +export type RecordingStatus = { + directoryIsSet: boolean; + recordingTelemetries: Set; +}; +type RecordingStatusListener = (status: RecordingStatus) => void; +let rootRecordDirectory: FileSystemDirectoryHandle | undefined; +const recorders: Map = new Map(); +let nextRecordingStatusListenerId = 0; +const recordStatusListeners: Map = new Map(); + +const currentRecordingStatus = (): RecordingStatus => { + return { + directoryIsSet: rootRecordDirectory !== undefined, + recordingTelemetries: new Set(recorders.keys()), + }; +}; + +const addRecordingStatusListener = ( + listener: RecordingStatusListener, +): number => { + listener(currentRecordingStatus()); + const id = nextRecordingStatusListenerId++; + recordStatusListeners.set(id, listener); + return id; +}; + +const removeRecordingStatusListener = (id: number): void => { + recordStatusListeners.delete(id); +}; + +const notifyRecordingStatusListener = (): void => { + const status = currentRecordingStatus(); + for (const listener of recordStatusListeners.values()) { + listener(status); + } +}; + +// TODO: flush on worker termination +class TelemetryRecorder { + telemetryName: string; + cancel: () => void = () => {}; + cancelled: Promise; + onStop: () => void; + constructor( + rootRecordDirectory: FileSystemDirectoryHandle, + telemetryName: string, + onStop: () => void, + ) { + this.telemetryName = telemetryName; + this.cancelled = new Promise((resolve) => { + this.cancel = () => resolve(null); + }); + this.onStop = onStop; + this.run(rootRecordDirectory); + } + + private async write( + recordDirectory: FileSystemDirectoryHandle, + writable: FileSystemWritableFileStream, + tmiv: Tmiv, + ) { + // if tmiv has @blob field, save it to a file + let blobFileName = ""; + const blob = tmiv.fields.find((field) => { + return field.name === "@blob"; + }); + if (blob !== undefined) { + if (blob.value.oneofKind == "bytes") { + const blobBytes = blob.value.bytes; + //FIXME: safer name consrtuction + const blobDirectory = await recordDirectory.getDirectoryHandle( + "blob_data", + { + create: true, + }, + ); + + // FIXME: readable time format? + // FIXME: avoid name collision + blobFileName = `${Date.now()}.dat`; + const recordFile = await blobDirectory.getFileHandle(blobFileName, { + create: true, + }); + const blobWritable = await recordFile.createWritable(); + await blobWritable.write(blobBytes); + await blobWritable.close(); + } + } + + const fields: { [key: string]: any } = {}; + for (const field of tmiv.fields) { + if (field.name.includes("@RAW")) { + continue; + } + const name = field.name; + let value = undefined; + if (field.name == "@blob") { + value = blobFileName; + } else if (field.value.oneofKind == "integer") { + value = Number(field.value.integer); + } else if (field.value.oneofKind == "string") { + value = field.value.string; + } else if (field.value.oneofKind == "double") { + value = field.value.double; + } else if (field.value.oneofKind == "enum") { + value = field.value.enum; + } else if (field.value.oneofKind == "bytes") { + value = field.value.bytes; + } + fields[name] = value; + } + await writable.write(JSON.stringify(fields)); + await writable.write("\n"); + } + + private async run(rootRecordDirectory: FileSystemDirectoryHandle) { + // FIXME: safer name construction + const recordDirectoryName = this.telemetryName; + const recordDirectory = await rootRecordDirectory.getDirectoryHandle( + recordDirectoryName, + { create: true }, + ); + // FIXME: readable time format? + const recordFile = await recordDirectory.getFileHandle( + `${Date.now()}.log`, + { + create: true, + }, + ); + + const telemetryStream = await server.openTelemetryStream( + this.telemetryName, + ); + const reader = telemetryStream.getReader(); + let writable = await recordFile.createWritable(); + let lastFlushTime = Date.now(); + + // eslint-disable-next-line no-constant-condition + while (true) { + const next = await Promise.race([reader.read(), this.cancelled]); + if (next === null) { + // cancelled + break; + } + if (next.done) { + break; + } + const tmiv = next.value; + + await this.write(recordDirectory, writable, tmiv); + if (Date.now() - lastFlushTime > 10000) { + lastFlushTime = Date.now(); + await writable.close(); + writable = await recordFile.createWritable({ keepExistingData: true }); + const size = (await recordFile.getFile()).size; + await writable.seek(size); + } + } + + await writable.close(); + this.onStop(); + } + + stop() { + // do not call onStop here + this.cancel(); + } +} + const server = { async getSatelliteSchema(): Promise { const { response } = await tmtcGenericC2a.getSatelliteSchema({}); @@ -90,9 +264,62 @@ const server = { }, }); }, + async lastTelemetryValue(tmivName: string): Promise { return telemetryLastValues.get(tmivName); }, + + async setRootRecordDirectory( + directory: FileSystemDirectoryHandle, + ): Promise { + if (rootRecordDirectory === undefined) { + rootRecordDirectory = directory; + } + }, + + async hasRecordDirectory(): Promise { + return rootRecordDirectory !== undefined; + }, + + async enableRecording(telemetryName: string): Promise { + if (rootRecordDirectory === undefined) { + return; + } + if (recorders.has(telemetryName)) { + return; + } + + const recorder = new TelemetryRecorder( + rootRecordDirectory, + telemetryName, + () => { + recorders.delete(telemetryName); + notifyRecordingStatusListener(); + }, + ); + recorders.set(telemetryName, recorder); + notifyRecordingStatusListener(); + }, + + async disableRecording(telemetryName: string): Promise { + const recorder = recorders.get(telemetryName); + if (recorder === undefined) { + return; + } + recorder.stop(); + }, + + async openRecordingStatusStream(): Promise> { + let id: number | undefined; + return new ReadableStream({ + start(controller) { + id = addRecordingStatusListener((status) => controller.enqueue(status)); + }, + cancel() { + removeRecordingStatusListener(id!); + }, + }); + }, }; self.addEventListener("connect", (e) => { diff --git a/devtools-frontend/tsconfig.json b/devtools-frontend/tsconfig.json index ec676f17..901a3243 100644 --- a/devtools-frontend/tsconfig.json +++ b/devtools-frontend/tsconfig.json @@ -6,7 +6,7 @@ }, "target": "ESNext", "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"], - "types": ["vite/client"], + "types": ["vite/client", "@types/wicg-file-system-access" ], "allowJs": false, "skipLibCheck": false, "esModuleInterop": false, From 03be8506b50ae31634bf3b19de94758a4af0078c Mon Sep 17 00:00:00 2001 From: KOBAYASHI Kazuhiro Date: Mon, 19 Feb 2024 16:30:03 +0900 Subject: [PATCH 2/7] Devtools: show toggle recording button in TelemetryView --- .../src/components/TelemetryView.tsx | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/devtools-frontend/src/components/TelemetryView.tsx b/devtools-frontend/src/components/TelemetryView.tsx index 31754e22..a87f8af1 100644 --- a/devtools-frontend/src/components/TelemetryView.tsx +++ b/devtools-frontend/src/components/TelemetryView.tsx @@ -6,6 +6,7 @@ import { useClient } from "./Layout"; import { useParams } from "react-router-dom"; import { Helmet } from "react-helmet-async"; import { TelemetrySchema } from "../proto/tmtc_generic_c2a"; +import type { RecordingStatus } from "../worker"; const buildTelemetryFieldTreeBlueprintFromSchema = ( tlm: TelemetrySchema, @@ -162,6 +163,76 @@ const InlineNamespaceContentCell: React.FC = ({ }; export const TelemetryView: React.FC = () => { + const { client } = useClient(); + const [recorderStatus, setRecordingStatus] = useState( + null, + ); + const params = useParams(); + const tmivName = params["tmivName"]!; + useEffect(() => { + const readerP = client + .openRecordingStatusStream() + .then((stream) => stream.getReader()); + let cancel; + const cancelP = new Promise((resolve) => (cancel = resolve)); + Promise.all([readerP, cancelP]).then(([reader]) => reader.cancel()); + readerP.then(async (reader) => { + // eslint-disable-next-line no-constant-condition + while (true) { + const next = await reader.read(); + if (next.done) { + break; + } + setRecordingStatus(next.value); + } + }); + return cancel; + }, [client]); + + const toggleRecordingStatus = async () => { + if (!recorderStatus?.directoryIsSet) { + const directoryHandle = await window.showDirectoryPicker({ + mode: "readwrite", + }); + client.setRootRecordDirectory(directoryHandle); + } + if (recorderStatus?.recordingTelemetries.has(tmivName)) { + client.disableRecording(tmivName); + } else { + client.enableRecording(tmivName); + } + }; + + + + const recording = + recorderStatus?.recordingTelemetries?.has(tmivName) ?? false; + + const recordingMenuItemsWhenRecording = ( + <> +
  • Recording: ON
  • +
  • Stop Recording
  • + + ); + const recordingMenuItemsWhenNotRecording = ( + <> +
  • Recording: OFF
  • +
  • Start Recording
  • + + ); + return ( + <> + + + + ); +}; + +const TelemetryViewBody: React.FC = () => { const params = useParams(); const tmivName = params["tmivName"]!; const { From d92d23ed2719957eafd313a9394a76a10d7dfef9 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Kazuhiro Date: Tue, 5 Mar 2024 10:14:23 +0900 Subject: [PATCH 3/7] Devtools: Fix TelemetryView's layout --- devtools-frontend/src/components/TelemetryView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devtools-frontend/src/components/TelemetryView.tsx b/devtools-frontend/src/components/TelemetryView.tsx index a87f8af1..b987596b 100644 --- a/devtools-frontend/src/components/TelemetryView.tsx +++ b/devtools-frontend/src/components/TelemetryView.tsx @@ -221,14 +221,14 @@ export const TelemetryView: React.FC = () => { ); return ( - <> +
    - +
    ); }; From b34a5c3be6a3c02ce679501b1a83a44db1979b3b Mon Sep 17 00:00:00 2001 From: KOBAYASHI Kazuhiro Date: Fri, 12 Apr 2024 15:41:04 +0900 Subject: [PATCH 4/7] devtools_frontend: Update package.json and pnpm-lock.yaml --- devtools-frontend/package.json | 3 ++- devtools-frontend/pnpm-lock.yaml | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/devtools-frontend/package.json b/devtools-frontend/package.json index a84db69b..5ff2853d 100644 --- a/devtools-frontend/package.json +++ b/devtools-frontend/package.json @@ -32,6 +32,7 @@ "@protobuf-ts/grpcweb-transport": "^2.8.2", "@protobuf-ts/runtime": "^2.9.3", "@protobuf-ts/runtime-rpc": "^2.9.3", + "c2a-devtools": "link:", "monaco-editor": "^0.44.0", "react": "18.2.0", "react-dom": "18.2.0", @@ -44,9 +45,9 @@ "@protobuf-ts/plugin": "^2.8.2", "@types/react": "18.2.65", "@types/react-dom": "18.2.21", + "@types/wicg-file-system-access": "^2023.10.4", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", - "@types/wicg-file-system-access": "^2023.10.4", "@vitejs/plugin-react": "4.2.1", "autoprefixer": "10.4.18", "eslint": "8.57.0", diff --git a/devtools-frontend/pnpm-lock.yaml b/devtools-frontend/pnpm-lock.yaml index e9004f99..f625aa08 100644 --- a/devtools-frontend/pnpm-lock.yaml +++ b/devtools-frontend/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: '@protobuf-ts/runtime-rpc': specifier: ^2.9.3 version: 2.9.3 + c2a-devtools: + specifier: 'link:' + version: 'link:' monaco-editor: specifier: ^0.44.0 version: 0.44.0 @@ -55,6 +58,9 @@ devDependencies: '@types/react-dom': specifier: 18.2.21 version: 18.2.21 + '@types/wicg-file-system-access': + specifier: ^2023.10.4 + version: 2023.10.5 '@typescript-eslint/eslint-plugin': specifier: 7.2.0 version: 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2) @@ -948,6 +954,10 @@ packages: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} dev: true + /@types/wicg-file-system-access@2023.10.5: + resolution: {integrity: sha512-e9kZO9kCdLqT2h9Tw38oGv9UNzBBWaR1MzuAavxPcsV/7FJ3tWbU6RI3uB+yKIDPGLkGVbplS52ub0AcRLvrhA==} + dev: true + /@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2): resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==} engines: {node: ^16.0.0 || >=18.0.0} From b52c612200f216e0172c4ff12940d25ece99eb34 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Kazuhiro Date: Fri, 12 Apr 2024 15:49:03 +0900 Subject: [PATCH 5/7] devtools_frotnend: format --- devtools-frontend/src/components/Layout.tsx | 24 +++++++++---------- .../src/components/TelemetryView.tsx | 4 +--- devtools-frontend/tsconfig.json | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/devtools-frontend/src/components/Layout.tsx b/devtools-frontend/src/components/Layout.tsx index ad0a7313..c1edb332 100644 --- a/devtools-frontend/src/components/Layout.tsx +++ b/devtools-frontend/src/components/Layout.tsx @@ -34,7 +34,7 @@ const TelemetryListSidebar: React.FC = ({ }) => { const recorder = (useLoaderData() as ClientContext).client; const [recorderStatus, setRecordingStatus] = useState( - null + null, ); useEffect(() => { const readerP = recorder @@ -120,15 +120,15 @@ const TelemetryListSidebar: React.FC = ({ }`} > toggleRecordingStatus(item.name)}> - {recorderStatus?.recordingTelemetries.has(item.name) - ?
    -
    -
    - :
    -
    -
    - - } + {recorderStatus?.recordingTelemetries.has(item.name) ? ( +
    +
    +
    + ) : ( +
    +
    +
    + )}
    { const items: TelemetryMenuItem[] = []; const channelNames = Object.keys(ctx.satelliteSchema.telemetryChannels); for (const [componentName, componentSchema] of Object.entries( - ctx.satelliteSchema.telemetryComponents + ctx.satelliteSchema.telemetryComponents, )) { for (const [telemetryName, telemetrySchema] of Object.entries( - componentSchema.telemetries + componentSchema.telemetries, )) { for (const channelName of channelNames) { const name = `${channelName}.${componentName}.${telemetryName}`; diff --git a/devtools-frontend/src/components/TelemetryView.tsx b/devtools-frontend/src/components/TelemetryView.tsx index b987596b..34517b71 100644 --- a/devtools-frontend/src/components/TelemetryView.tsx +++ b/devtools-frontend/src/components/TelemetryView.tsx @@ -203,9 +203,7 @@ export const TelemetryView: React.FC = () => { } }; - - - const recording = + const recording = recorderStatus?.recordingTelemetries?.has(tmivName) ?? false; const recordingMenuItemsWhenRecording = ( diff --git a/devtools-frontend/tsconfig.json b/devtools-frontend/tsconfig.json index 901a3243..1d430f30 100644 --- a/devtools-frontend/tsconfig.json +++ b/devtools-frontend/tsconfig.json @@ -6,7 +6,7 @@ }, "target": "ESNext", "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"], - "types": ["vite/client", "@types/wicg-file-system-access" ], + "types": ["vite/client", "@types/wicg-file-system-access"], "allowJs": false, "skipLibCheck": false, "esModuleInterop": false, From 7419fb2ffe01ba938b153de05685ddf00a4c4ba7 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Kazuhiro Date: Fri, 12 Apr 2024 15:55:26 +0900 Subject: [PATCH 6/7] devtools_frontend: remove blob recording --- devtools-frontend/src/worker.ts | 40 +++------------------------------ 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/devtools-frontend/src/worker.ts b/devtools-frontend/src/worker.ts index 190620d9..d6dfcd69 100644 --- a/devtools-frontend/src/worker.ts +++ b/devtools-frontend/src/worker.ts @@ -124,39 +124,7 @@ class TelemetryRecorder { this.run(rootRecordDirectory); } - private async write( - recordDirectory: FileSystemDirectoryHandle, - writable: FileSystemWritableFileStream, - tmiv: Tmiv, - ) { - // if tmiv has @blob field, save it to a file - let blobFileName = ""; - const blob = tmiv.fields.find((field) => { - return field.name === "@blob"; - }); - if (blob !== undefined) { - if (blob.value.oneofKind == "bytes") { - const blobBytes = blob.value.bytes; - //FIXME: safer name consrtuction - const blobDirectory = await recordDirectory.getDirectoryHandle( - "blob_data", - { - create: true, - }, - ); - - // FIXME: readable time format? - // FIXME: avoid name collision - blobFileName = `${Date.now()}.dat`; - const recordFile = await blobDirectory.getFileHandle(blobFileName, { - create: true, - }); - const blobWritable = await recordFile.createWritable(); - await blobWritable.write(blobBytes); - await blobWritable.close(); - } - } - + private async write(writable: FileSystemWritableFileStream, tmiv: Tmiv) { const fields: { [key: string]: any } = {}; for (const field of tmiv.fields) { if (field.name.includes("@RAW")) { @@ -164,9 +132,7 @@ class TelemetryRecorder { } const name = field.name; let value = undefined; - if (field.name == "@blob") { - value = blobFileName; - } else if (field.value.oneofKind == "integer") { + if (field.value.oneofKind == "integer") { value = Number(field.value.integer); } else if (field.value.oneofKind == "string") { value = field.value.string; @@ -217,7 +183,7 @@ class TelemetryRecorder { } const tmiv = next.value; - await this.write(recordDirectory, writable, tmiv); + await this.write(writable, tmiv); if (Date.now() - lastFlushTime > 10000) { lastFlushTime = Date.now(); await writable.close(); From 38c21694994a1ff4fdc7d3d124b45ac3d57d22db Mon Sep 17 00:00:00 2001 From: KOBAYASHI Kazuhiro Date: Thu, 18 Apr 2024 14:36:11 +0900 Subject: [PATCH 7/7] Use Button component for recording buttons --- devtools-frontend/src/components/TelemetryView.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/devtools-frontend/src/components/TelemetryView.tsx b/devtools-frontend/src/components/TelemetryView.tsx index 34517b71..029acf71 100644 --- a/devtools-frontend/src/components/TelemetryView.tsx +++ b/devtools-frontend/src/components/TelemetryView.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { TreeNamespace, addToNamespace, mapNamespace } from "../tree"; +import { Button } from "@blueprintjs/core"; import { Tmiv, TmivField } from "../proto/tco_tmiv"; import { useClient } from "./Layout"; @@ -208,14 +209,14 @@ export const TelemetryView: React.FC = () => { const recordingMenuItemsWhenRecording = ( <> -
  • Recording: ON
  • -
  • Stop Recording
  • +

    Recording: ON

    + ); const recordingMenuItemsWhenNotRecording = ( <> -
  • Recording: OFF
  • -
  • Start Recording
  • +

    Recording: OFF

    + ); return (