-
Notifications
You must be signed in to change notification settings - Fork 449
Add a menu to copy the Marker Table as text #5732
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ import { | |
| } from '../../actions/profile-view'; | ||
| import { MarkerSettings } from '../shared/MarkerSettings'; | ||
| import { formatSeconds, formatTimestamp } from '../../utils/format-numbers'; | ||
| import copy from 'copy-to-clipboard'; | ||
|
|
||
| import './index.css'; | ||
|
|
||
|
|
@@ -48,6 +49,8 @@ type MarkerDisplayData = { | |
| details: string; | ||
| }; | ||
|
|
||
| function assertExhaustiveCheck(_param: never) {} | ||
|
|
||
| class MarkerTree { | ||
| _getMarker: (param: MarkerIndex) => Marker; | ||
| _markerIndexes: MarkerIndex[]; | ||
|
|
@@ -71,6 +74,102 @@ class MarkerTree { | |
| this._getMarkerLabel = getMarkerLabel; | ||
| } | ||
|
|
||
| copyTable = ( | ||
| format: 'plain' | 'markdown', | ||
| onWarning: (message: string) => void | ||
| ) => { | ||
| const lines = []; | ||
|
|
||
| const startLabel = 'Start'; | ||
| const durationLabel = 'Duration'; | ||
| const nameLabel = 'Name'; | ||
| const detailsLabel = 'Details'; | ||
|
|
||
| const header = [startLabel, durationLabel, nameLabel, detailsLabel]; | ||
|
|
||
| let maxStartLength = startLabel.length; | ||
| let maxDurationLength = durationLabel.length; | ||
| let maxNameLength = nameLabel.length; | ||
|
|
||
| const MAX_COPY_ROWS = 10000; | ||
|
|
||
| let roots = this.getRoots(); | ||
| if (roots.length > MAX_COPY_ROWS) { | ||
| onWarning( | ||
| `The number of rows hits the limit: ${roots.length} > ${MAX_COPY_ROWS}` | ||
| ); | ||
| roots = roots.slice(0, MAX_COPY_ROWS); | ||
| } | ||
|
|
||
| for (const index of roots) { | ||
| const data = this.getDisplayData(index); | ||
| const duration = data.duration ?? ''; | ||
|
|
||
| maxStartLength = Math.max(data.start.length, maxStartLength); | ||
| maxDurationLength = Math.max(duration.length, maxDurationLength); | ||
| maxNameLength = Math.max(data.name.length, maxNameLength); | ||
|
|
||
| lines.push([ | ||
| data.start, | ||
| // Use "u" instead, to make the table aligned with fixed-width text. | ||
| duration.replace(/μ/g, 'u'), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we replace
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because " |
||
| data.name, | ||
| data.details, | ||
| ]); | ||
| } | ||
|
|
||
| let text = ''; | ||
| switch (format) { | ||
| case 'plain': { | ||
| const formatter = ([start, duration, name, details]: string[]) => { | ||
| const line = [ | ||
| start.padStart(maxStartLength, ' '), | ||
| duration.padStart(maxDurationLength, ' '), | ||
| name.padStart(maxNameLength, ' '), | ||
| ]; | ||
| if (details) { | ||
| line.push(details); | ||
| } | ||
| return line.join(' '); | ||
| }; | ||
|
|
||
| text += formatter(header) + '\n' + lines.map(formatter).join('\n'); | ||
| break; | ||
| } | ||
| case 'markdown': { | ||
| const formatter = ([start, duration, name, details]: string[]) => { | ||
| const line = [ | ||
| start.padStart(maxStartLength, ' '), | ||
| duration.padStart(maxDurationLength, ' '), | ||
| name.padStart(maxNameLength, ' '), | ||
| details, | ||
| ]; | ||
| return '| ' + line.join(' | ') + ' |'; | ||
| }; | ||
| const sep = | ||
| '|' + | ||
| [ | ||
| '-'.repeat(maxStartLength + 1) + ':', | ||
| '-'.repeat(maxDurationLength + 1) + ':', | ||
| '-'.repeat(maxNameLength + 1) + ':', | ||
| '-'.repeat(9), | ||
| ].join('|') + | ||
| '|'; | ||
| text = | ||
| formatter(header) + | ||
| '\n' + | ||
| sep + | ||
| '\n' + | ||
| lines.map(formatter).join('\n'); | ||
| break; | ||
| } | ||
| default: | ||
| assertExhaustiveCheck(format); | ||
| } | ||
|
|
||
| copy(text); | ||
| }; | ||
|
|
||
| getRoots(): MarkerIndex[] { | ||
| return this._markerIndexes; | ||
| } | ||
|
|
@@ -263,7 +362,7 @@ class MarkerTableImpl extends PureComponent<Props> { | |
| role="tabpanel" | ||
| aria-labelledby="marker-table-tab-button" | ||
| > | ||
| <MarkerSettings /> | ||
| <MarkerSettings copyTable={tree.copyTable} /> | ||
| {markerIndexes.length === 0 ? ( | ||
| <MarkerTableEmptyReasons /> | ||
| ) : ( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
| import { PureComponent } from 'react'; | ||
| import { MenuItem } from '@firefox-devtools/react-contextmenu'; | ||
| import { Localized } from '@fluent/react'; | ||
|
|
||
| import { ContextMenu } from './ContextMenu'; | ||
| import explicitConnect from 'firefox-profiler/utils/connect'; | ||
|
|
||
| import type { ConnectedProps } from 'firefox-profiler/utils/connect'; | ||
|
|
||
| type OwnProps = { | ||
| readonly onShow: () => void; | ||
| readonly onHide: () => void; | ||
| readonly onCopy: (format: 'plain' | 'markdown') => void; | ||
| }; | ||
|
|
||
| type Props = ConnectedProps<OwnProps, {}, {}>; | ||
|
|
||
| class MarkerCopyTableContextMenuImpl extends PureComponent<Props> { | ||
| copyAsPlain = () => { | ||
| const { onCopy } = this.props; | ||
| onCopy('plain'); | ||
| }; | ||
|
|
||
| copyAsMarkdown = () => { | ||
| const { onCopy } = this.props; | ||
| onCopy('markdown'); | ||
| }; | ||
|
|
||
| override render() { | ||
| const { onShow, onHide } = this.props; | ||
| return ( | ||
| <ContextMenu | ||
| id="MarkerCopyTableContextMenu" | ||
| className="markerCopyTableContextMenu" | ||
| onShow={onShow} | ||
| onHide={onHide} | ||
| > | ||
| <MenuItem onClick={this.copyAsPlain}> | ||
| <Localized id="MarkerCopyTableContextMenu--copy-table-as-plain"> | ||
| Copy marker table as plain text | ||
| </Localized> | ||
| </MenuItem> | ||
| <MenuItem onClick={this.copyAsMarkdown}> | ||
| <Localized id="MarkerCopyTableContextMenu--copy-table-as-markdown"> | ||
| Copy marker table as Markdown | ||
| </Localized> | ||
| </MenuItem> | ||
| </ContextMenu> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export const MarkerCopyTableContextMenu = explicitConnect<OwnProps, {}, {}>({ | ||
| component: MarkerCopyTableContextMenuImpl, | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.