Define a new widget for entities that have a dropdown menu that are not links (anchors)
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss index 7364327..943a5f2 100644 --- a/ui/src/assets/perfetto.scss +++ b/ui/src/assets/perfetto.scss
@@ -56,6 +56,7 @@ @import "widgets/hotkey_context"; @import "widgets/hotkey"; @import "widgets/icon"; +@import "widgets/interactive_text"; @import "widgets/linear_progress"; @import "widgets/menu"; @import "widgets/middle_ellipsis";
diff --git a/ui/src/assets/widgets/interactive_text.scss b/ui/src/assets/widgets/interactive_text.scss new file mode 100644 index 0000000..bd1b1c5 --- /dev/null +++ b/ui/src/assets/widgets/interactive_text.scss
@@ -0,0 +1,38 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// 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 "../theme"; + +.pf-interactive-text { + color: inherit; + border-radius: $border-radius; + cursor: pointer; + text-decoration: none; + + &__icon--end { + margin-left: 1px; + } + + &__icon--start { + margin-right: 1px; + } + + &:hover { + background: color-hover-surface(); + } + + &:focus-visible { + @include focus; + } +}
diff --git a/ui/src/components/details/args.ts b/ui/src/components/details/args.ts index 9fd4ff3..d3b5248 100644 --- a/ui/src/components/details/args.ts +++ b/ui/src/components/details/args.ts
@@ -21,6 +21,7 @@ import {TreeNode} from '../../widgets/tree'; import {Args, ArgsDict, ArgValue} from '../sql_utils/args'; import {Trace} from '../../public/trace'; +import {InteractiveText} from '../../widgets/interactive_text'; // Renders slice arguments (key/value pairs) as a subtree. export function renderArguments( @@ -110,7 +111,7 @@ } else { return m( PopupMenu, - {trigger: m(Anchor, {icon: Icons.ContextMenu}, key)}, + {trigger: m(InteractiveText, {icon: Icons.ContextMenu}, key)}, m(MenuItem, { label: 'Copy full key', icon: 'content_copy',
diff --git a/ui/src/components/details/slice_details.ts b/ui/src/components/details/slice_details.ts index 8929aa2..ffed05c 100644 --- a/ui/src/components/details/slice_details.ts +++ b/ui/src/components/details/slice_details.ts
@@ -17,7 +17,6 @@ import {sqliteString} from '../../base/string_utils'; import {exists} from '../../base/utils'; import {SliceDetails} from '../sql_utils/slice'; -import {Anchor} from '../../widgets/anchor'; import {MenuItem, PopupMenu} from '../../widgets/menu'; import {Section} from '../../widgets/section'; import {SqlRef} from '../../widgets/sql_ref'; @@ -33,6 +32,7 @@ import {Trace} from '../../public/trace'; import {extensions} from '../extensions'; import {SLICE_TABLE} from '../widgets/sql/table_definitions'; +import {InteractiveText} from '../../widgets/interactive_text'; // Renders a widget storing all of the generic details for a slice from the // slice table. @@ -51,7 +51,7 @@ right: m( PopupMenu, { - trigger: m(Anchor, slice.name), + trigger: m(InteractiveText, slice.name), }, m(MenuItem, { label: 'Slices with the same name',
diff --git a/ui/src/components/widgets/datagrid/datagrid.ts b/ui/src/components/widgets/datagrid/datagrid.ts index 152a0db..be5d249 100644 --- a/ui/src/components/widgets/datagrid/datagrid.ts +++ b/ui/src/components/widgets/datagrid/datagrid.ts
@@ -20,7 +20,7 @@ import {shortUuid} from '../../../base/uuid'; import {exists, isNumeric, maybeUndefined} from '../../../base/utils'; import {Row, SqlValue} from '../../../trace_processor/query_result'; -import {Anchor} from '../../../widgets/anchor'; +import {InteractiveText} from '../../../widgets/interactive_text'; import {Button, ButtonGroup, ButtonVariant} from '../../../widgets/button'; import {EmptyState} from '../../../widgets/empty_state'; import { @@ -2305,7 +2305,7 @@ return ''; } else if (value instanceof Uint8Array) { return m( - Anchor, + InteractiveText, { icon: Icons.Download, onclick: () =>
diff --git a/ui/src/components/widgets/duration.ts b/ui/src/components/widgets/duration.ts index a6bd3d1..b934a3c 100644 --- a/ui/src/components/widgets/duration.ts +++ b/ui/src/components/widgets/duration.ts
@@ -16,12 +16,12 @@ import {copyToClipboard} from '../../base/clipboard'; import {Icons} from '../../base/semantic_icons'; import {duration} from '../../base/time'; -import {Anchor} from '../../widgets/anchor'; import {MenuDivider, MenuItem, PopupMenu} from '../../widgets/menu'; import {Trace} from '../../public/trace'; import {formatDuration} from '../time_utils'; import {DurationPrecisionMenuItem} from './duration_precision_menu_items'; import {TimestampFormatMenuItem} from './timestamp_format_menu'; +import {InteractiveText} from '../../widgets/interactive_text'; interface DurationWidgetAttrs { trace: Trace; @@ -39,7 +39,7 @@ return m( PopupMenu, { - trigger: m(Anchor, value), + trigger: m(InteractiveText, {icon: Icons.ContextMenu}, value), }, m(MenuItem, { icon: Icons.Copy,
diff --git a/ui/src/components/widgets/process.ts b/ui/src/components/widgets/process.ts index 943afe3..ad5fe2e 100644 --- a/ui/src/components/widgets/process.ts +++ b/ui/src/components/widgets/process.ts
@@ -24,7 +24,6 @@ ProcessInfo, } from '../sql_utils/process'; import {Trace} from '../../public/trace'; -import {Anchor} from '../../widgets/anchor'; import {MenuItem, PopupMenu} from '../../widgets/menu'; import {ProcessDetailsTab} from '../details/process_details_tab'; import { @@ -32,6 +31,7 @@ sqlIdRegistry, } from '../widgets/sql/details/sql_ref_renderer_registry'; import {asUpid} from '../sql_utils/core_types'; +import {InteractiveText} from '../../widgets/interactive_text'; export function showProcessDetailsMenuItem( trace: Trace, @@ -91,7 +91,7 @@ return m( PopupMenu, { - trigger: m(Anchor, getProcessName(info)), + trigger: m(InteractiveText, getProcessName(info)), }, processRefMenuItems(trace, info), );
diff --git a/ui/src/components/widgets/sched.ts b/ui/src/components/widgets/sched.ts index c01c19a..ab39c55 100644 --- a/ui/src/components/widgets/sched.ts +++ b/ui/src/components/widgets/sched.ts
@@ -14,9 +14,9 @@ import m from 'mithril'; import {SchedSqlId} from '../sql_utils/core_types'; -import {Anchor} from '../../widgets/anchor'; import {Icons} from '../../base/semantic_icons'; import {Trace} from '../../public/trace'; +import {InteractiveText} from '../../widgets/interactive_text'; interface SchedRefAttrs { readonly trace: Trace; @@ -42,7 +42,7 @@ export class SchedRef implements m.ClassComponent<SchedRefAttrs> { view(vnode: m.Vnode<SchedRefAttrs>) { return m( - Anchor, + InteractiveText, { icon: Icons.UpdateSelection, onclick: () => {
diff --git a/ui/src/components/widgets/sql/table/columns.ts b/ui/src/components/widgets/sql/table/columns.ts index 0527041..2467f90 100644 --- a/ui/src/components/widgets/sql/table/columns.ts +++ b/ui/src/components/widgets/sql/table/columns.ts
@@ -46,7 +46,7 @@ PerfettoSqlTypes, } from '../../../../trace_processor/perfetto_sql_type'; import {parseJsonWithBigints} from '../../../../base/json_utils'; -import {Anchor} from '../../../../widgets/anchor'; +import {InteractiveText} from '../../../../widgets/interactive_text'; import {MenuItem, PopupMenu} from '../../../../widgets/menu'; import {Icons} from '../../../../base/semantic_icons'; import {copyToClipboard} from '../../../../base/clipboard'; @@ -724,7 +724,7 @@ ? m( PopupMenu, { - trigger: m(Anchor, key), + trigger: m(InteractiveText, key), }, m(MenuItem, { icon: Icons.Add,
diff --git a/ui/src/components/widgets/thread.ts b/ui/src/components/widgets/thread.ts index 71a06d4..3120953 100644 --- a/ui/src/components/widgets/thread.ts +++ b/ui/src/components/widgets/thread.ts
@@ -18,7 +18,6 @@ import {exists} from '../../base/utils'; import {addEphemeralTab} from '../details/add_ephemeral_tab'; import {getThreadInfo, getThreadName, ThreadInfo} from '../sql_utils/thread'; -import {Anchor} from '../../widgets/anchor'; import {MenuItem, PopupMenu} from '../../widgets/menu'; import {ThreadDetailsTab} from '../details/thread_details_tab'; import { @@ -28,6 +27,7 @@ import {asUtid} from '../sql_utils/core_types'; import {Utid} from '../sql_utils/core_types'; import {Trace} from '../../public/trace'; +import {InteractiveText} from '../../widgets/interactive_text'; export function showThreadDetailsMenuItem( trace: Trace, @@ -95,7 +95,7 @@ return m( PopupMenu, { - trigger: m(Anchor, getThreadName(info)), + trigger: m(InteractiveText, getThreadName(info)), }, threadRefMenuItems(trace, info), );
diff --git a/ui/src/components/widgets/thread_state.ts b/ui/src/components/widgets/thread_state.ts index 9c92280..37fe3f8 100644 --- a/ui/src/components/widgets/thread_state.ts +++ b/ui/src/components/widgets/thread_state.ts
@@ -14,7 +14,7 @@ import m from 'mithril'; import {ThreadStateSqlId} from '../sql_utils/core_types'; -import {Anchor} from '../../widgets/anchor'; +import {InteractiveText} from '../../widgets/interactive_text'; import {Icons} from '../../base/semantic_icons'; import {ThreadState} from '../sql_utils/thread_state'; import {Trace} from '../../public/trace'; @@ -35,7 +35,7 @@ export class ThreadStateRef implements m.ClassComponent<ThreadStateRefAttrs> { view(vnode: m.Vnode<ThreadStateRefAttrs>) { return m( - Anchor, + InteractiveText, { icon: Icons.UpdateSelection, onclick: () => {
diff --git a/ui/src/components/widgets/timestamp.ts b/ui/src/components/widgets/timestamp.ts index c6e20bd..60f6394 100644 --- a/ui/src/components/widgets/timestamp.ts +++ b/ui/src/components/widgets/timestamp.ts
@@ -16,24 +16,25 @@ import {copyToClipboard} from '../../base/clipboard'; import {Icons} from '../../base/semantic_icons'; import {time, Time} from '../../base/time'; -import {Anchor} from '../../widgets/anchor'; import {MenuDivider, MenuItem, PopupMenu} from '../../widgets/menu'; import {Trace} from '../../public/trace'; import {TimestampFormatMenuItem} from './timestamp_format_menu'; import {renderTimecode} from '../time_utils'; import {TimestampFormat} from '../../public/timeline'; +import {InteractiveText} from '../../widgets/interactive_text'; // import {MenuItem, PopupMenu2} from './menu'; interface TimestampAttrs { - trace: Trace; + readonly trace: Trace; // The timestamp to print, this should be the absolute, raw timestamp as // found in trace processor. - ts: time; + readonly ts: time; // Custom text value to show instead of the default HH:MM:SS.mmm uuu nnn // formatting. - display?: m.Children; - extraMenuItems?: m.Child[]; + readonly display?: m.Children; + // Additional menu items to show in the popup menu + readonly extraMenuItems?: m.Child[]; } export class Timestamp implements m.ClassComponent<TimestampAttrs> { @@ -44,8 +45,9 @@ PopupMenu, { trigger: m( - Anchor, + InteractiveText, { + icon: Icons.ContextMenu, onmouseover: () => (timeline.hoverCursorTimestamp = ts), onmouseout: () => (timeline.hoverCursorTimestamp = undefined), },
diff --git a/ui/src/components/widgets/track_event_ref.ts b/ui/src/components/widgets/track_event_ref.ts index 19eae5b..d756cd6 100644 --- a/ui/src/components/widgets/track_event_ref.ts +++ b/ui/src/components/widgets/track_event_ref.ts
@@ -13,7 +13,7 @@ // limitations under the License. import m from 'mithril'; -import {Anchor} from '../../widgets/anchor'; +import {InteractiveText} from '../../widgets/interactive_text'; import {Icons} from '../../base/semantic_icons'; import {Trace} from '../../public/trace'; @@ -34,7 +34,7 @@ export class TrackEventRef implements m.ClassComponent<TrackEventRefAttrs> { view(vnode: m.Vnode<TrackEventRefAttrs>) { return m( - Anchor, + InteractiveText, { icon: Icons.UpdateSelection, onclick: () => {
diff --git a/ui/src/plugins/com.android.AndroidInputLifecycle/tab.ts b/ui/src/plugins/com.android.AndroidInputLifecycle/tab.ts index 88232db..2bbd80f 100644 --- a/ui/src/plugins/com.android.AndroidInputLifecycle/tab.ts +++ b/ui/src/plugins/com.android.AndroidInputLifecycle/tab.ts
@@ -28,7 +28,7 @@ UNKNOWN, } from '../../trace_processor/query_result'; import {Dataset, UnionDatasetWithLineage} from '../../trace_processor/dataset'; -import {Anchor} from '../../widgets/anchor'; +import {InteractiveText} from '../../widgets/interactive_text'; import {Icons} from '../../base/semantic_icons'; import {Checkbox} from '../../widgets/checkbox'; import {LifecycleOverlay} from './overlay'; @@ -572,7 +572,7 @@ ? m(DurationWidget, {dur, trace: this.trace}) : m('span', '-'), nav && - m(Anchor, { + m(InteractiveText, { icon: Icons.GoTo, onclick: () => this.goTo(nav), title: 'Go to event slice',
diff --git a/ui/src/plugins/dev.perfetto.QueryPage/query_table.ts b/ui/src/plugins/dev.perfetto.QueryPage/query_table.ts index 8379bff..a0e9f0b 100644 --- a/ui/src/plugins/dev.perfetto.QueryPage/query_table.ts +++ b/ui/src/plugins/dev.perfetto.QueryPage/query_table.ts
@@ -19,14 +19,18 @@ import {DetailsShell} from '../../widgets/details_shell'; import {Trace} from '../../public/trace'; import {Icons} from '../../base/semantic_icons'; -import {DataGrid, renderCell, DataGridApi} from '../../components/widgets/datagrid/datagrid'; +import { + DataGrid, + renderCell, + DataGridApi, +} from '../../components/widgets/datagrid/datagrid'; import { CellRenderer, ColumnSchema, SchemaRegistry, } from '../../components/widgets/datagrid/datagrid_schema'; import {InMemoryDataSource} from '../../components/widgets/datagrid/in_memory_data_source'; -import {Anchor} from '../../widgets/anchor'; +import {InteractiveText} from '../../widgets/interactive_text'; import {Box} from '../../widgets/box'; import {DataGridExportButton} from '../../components/widgets/datagrid/export_button'; import {CopyToClipboardButton} from '../../widgets/copy_to_clipboard_button'; @@ -281,7 +285,7 @@ const resolved = this.resolveIdTable(row, value); if (resolved !== undefined) { return m( - Anchor, + InteractiveText, { title: `Go to ${resolved.table} on the timeline`, icon: Icons.UpdateSelection,
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/interactive_text_demo.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/interactive_text_demo.ts new file mode 100644 index 0000000..a2df24f --- /dev/null +++ b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/interactive_text_demo.ts
@@ -0,0 +1,57 @@ +// Copyright (C) 2025 The Android Open Source Project +// +// 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 m from 'mithril'; +import {InteractiveText} from '../../../widgets/interactive_text'; +import {renderWidgetShowcase} from '../widgets_page_utils'; +import {Icons} from '../../../base/semantic_icons'; + +export function renderInteractiveText(): m.Children { + return [ + m( + '.pf-widget-intro', + m('h1', 'InteractiveText'), + m('p', [ + 'A clickable widget that looks like regular text (no blue color or ' + + 'underline) but shows a gray background on hover.', + ]), + ), + + renderWidgetShowcase({ + renderWidget: ({icon, showInlineWithText, long}) => + m('', [ + showInlineWithText && 'Inline ', + m( + InteractiveText, + { + icon: icon ? Icons.ExternalLink : undefined, + onclick: () => alert('Clicked!'), + }, + long + ? 'This is some really really really long text and hopefully it ' + + 'will overflow the container in order to demonstrate how long ' + + 'text within interactive texts behaves.' + : 'InteractiveText', + ), + showInlineWithText && ' text', + ]), + + initialOpts: { + icon: true, + showInlineWithText: false, + long: false, + }, + }), + ]; +}
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tree_demo.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tree_demo.ts index c7d0462..f7d2ba1 100644 --- a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tree_demo.ts +++ b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tree_demo.ts
@@ -16,7 +16,7 @@ import {LazyTreeNode, Tree, TreeNode} from '../../../widgets/tree'; import {PopupMenu} from '../../../widgets/menu'; import {MenuItem} from '../../../widgets/menu'; -import {Anchor} from '../../../widgets/anchor'; +import {InteractiveText} from '../../../widgets/interactive_text'; import {PopupPosition} from '../../../widgets/popup'; import {Icons} from '../../../base/semantic_icons'; import {renderWidgetShowcase} from '../widgets_page_utils'; @@ -62,7 +62,7 @@ { position: PopupPosition.RightStart, trigger: m( - Anchor, + InteractiveText, { icon: Icons.ContextMenu, }, @@ -82,11 +82,11 @@ m(TreeNode, { icon: 'account_tree', left: 'Process', - right: m(Anchor, {icon: 'open_in_new'}, '/bin/foo[789]'), + right: m(InteractiveText, {icon: 'open_in_new'}, '/bin/foo[789]'), }), m(TreeNode, { left: 'Thread', - right: m(Anchor, {icon: 'open_in_new'}, 'my_thread[456]'), + right: m(InteractiveText, {icon: 'open_in_new'}, 'my_thread[456]'), }), m( TreeNode,
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts index ce9cb39..07f0bea 100644 --- a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts +++ b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
@@ -33,6 +33,7 @@ import {renderEmptyState} from './demos/empty_state_demo'; import {renderForm} from './demos/form_demo'; import {renderGrid} from './demos/grid_demo'; +import {renderInteractiveText} from './demos/interactive_text_demo'; import {renderCharts} from './demos/charts_demo'; import {renderHotkey} from './demos/hotkey_demo'; import {renderIcon} from './demos/icon_demo'; @@ -87,6 +88,11 @@ {id: 'emptystate', label: 'EmptyState', view: renderEmptyState}, {id: 'form', label: 'Form', view: renderForm}, {id: 'grid', label: 'Grid', view: renderGrid}, + { + id: 'interactive-text', + label: 'InteractiveText', + view: renderInteractiveText, + }, {id: 'hotkey', label: 'Hotkey', view: renderHotkey}, {id: 'icon', label: 'Icon', view: renderIcon}, {id: 'menu', label: 'Menu', view: renderMenu},
diff --git a/ui/src/widgets/button.ts b/ui/src/widgets/button.ts index 1aa2854..43e4e3e 100644 --- a/ui/src/widgets/button.ts +++ b/ui/src/widgets/button.ts
@@ -18,7 +18,7 @@ import {Icon} from './icon'; import {Popup, PopupPosition} from './popup'; import {assertUnreachable} from '../base/assert'; -import {isEmptyVnodes} from '../base/mithril_utils'; +import {hasChildren, isEmptyVnodes} from '../base/mithril_utils'; import {Tooltip} from './tooltip'; export enum ButtonVariant { @@ -78,7 +78,7 @@ interface LabelButtonAttrs extends CommonAttrs { // Label buttons require a label. - readonly label: string; + readonly label?: string; // Label buttons can have an optional icon. readonly icon?: string; } @@ -90,7 +90,8 @@ } export class Button implements m.ClassComponent<ButtonAttrs> { - view({attrs}: m.CVnode<ButtonAttrs>) { + view(vnode: m.CVnode<ButtonAttrs>) { + const {attrs} = vnode; const { icon, active, @@ -108,8 +109,11 @@ ...htmlAttrs } = attrs; - const label = isLabelButtonAttrs(attrs) ? attrs.label : undefined; - const iconOnly = Boolean(icon && !label); + let label: m.Children = isLabelButtonAttrs(attrs) ? attrs.label : undefined; + if (hasChildren(vnode)) { + label = vnode.children; + } + const iconOnly = Boolean(icon && !Boolean(label)); const classes = classNames( active && 'pf-active',
diff --git a/ui/src/widgets/copyable_link.ts b/ui/src/widgets/copyable_link.ts index 9244107..6ae96ba 100644 --- a/ui/src/widgets/copyable_link.ts +++ b/ui/src/widgets/copyable_link.ts
@@ -14,7 +14,7 @@ import m from 'mithril'; import {copyToClipboard} from '../base/clipboard'; -import {Anchor} from './anchor'; +import {InteractiveText} from './interactive_text'; interface CopyableLinkAttrs { url: string; @@ -28,7 +28,7 @@ return m( 'div', m( - Anchor, + InteractiveText, { href: url, title: 'Click to copy the URL into the clipboard',
diff --git a/ui/src/widgets/interactive_text.ts b/ui/src/widgets/interactive_text.ts new file mode 100644 index 0000000..ad6d330 --- /dev/null +++ b/ui/src/widgets/interactive_text.ts
@@ -0,0 +1,47 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// 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 m from 'mithril'; +import {HTMLAnchorAttrs} from './common'; +import {Icon} from './icon'; + +export interface InteractiveTextAttrs extends HTMLAnchorAttrs { + // Optional icon to show at the end of the content. + readonly icon?: string; + + // Optional icon to show at the start of the content. + readonly startIcon?: string; +} + +/** + * A clickable widget that looks like regular text (no blue color or underline) + * but shows a gray background on hover. + */ +export class InteractiveText implements m.ClassComponent<InteractiveTextAttrs> { + view({attrs, children}: m.CVnode<InteractiveTextAttrs>) { + const {icon, startIcon, ...htmlAttrs} = attrs; + + return m( + 'a.pf-interactive-text', + htmlAttrs, + startIcon && + m(Icon, { + icon: startIcon, + className: 'pf-interactive-text__icon--start', + }), + children, + icon && m(Icon, {icon, className: 'pf-interactive-text__icon--end'}), + ); + } +}
diff --git a/ui/src/widgets/sql_ref.ts b/ui/src/widgets/sql_ref.ts index fbefab0..63db361 100644 --- a/ui/src/widgets/sql_ref.ts +++ b/ui/src/widgets/sql_ref.ts
@@ -15,8 +15,8 @@ import m from 'mithril'; import {copyToClipboard} from '../base/clipboard'; import {Icons} from '../base/semantic_icons'; -import {Anchor} from './anchor'; import {MenuItem, PopupMenu} from './menu'; +import {InteractiveText} from './interactive_text'; // This widget provides common styling and popup menu options for a SQL row, // given a table name and an ID. @@ -41,7 +41,7 @@ PopupMenu, { trigger: m( - Anchor, + InteractiveText, {icon: Icons.ContextMenu}, `${table}[${idColumnName === 'id' ? '' : `${idColumnName}=`}${id}]`, ),