@backstage/core-components 0.8.6 → 0.8.8-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @backstage/core-components
2
2
 
3
+ ## 0.8.8-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 8d785a0b1b: chore: bump `ansi-regex` from `5.0.1` to `6.0.1`
8
+ - f2dfbd3fb0: Adjust ErrorPage to accept optional supportUrl property to override app support config. Update type of additionalInfo property to be ReactNode to accept both string and component.
9
+ - d62bdb7a8e: The `ErrorPage` now falls back to using the default support configuration if the `ConfigApi` is not available.
10
+
11
+ ## 0.8.7
12
+
13
+ ### Patch Changes
14
+
15
+ - f7257dff6f: The `<Link />` component now accepts a `noTrack` prop, which prevents the `click` event from being captured by the Analytics API. This can be used if tracking is explicitly not warranted, or in order to use custom link tracking in specific situations.
16
+ - 4c773ed25c: Change subtitle of Header style to use palette.bursts.fontColor
17
+ - f465b63b7f: Fix an issue where changes related to the `MobileSidebar` prevented scrolling pages. Additionally improve the menu of the `MobileSidebar` to not overlay the `BottomNavigation`.
18
+ - 064e750a50: Adding hover message to the Gauge and an info icon to the GaugeCard.
19
+ - a681cb9c2f: Make linkTarget configurable for MarkdownContent component
20
+
21
+ ## 0.8.7-next.1
22
+
23
+ ### Patch Changes
24
+
25
+ - f7257dff6f: The `<Link />` component now accepts a `noTrack` prop, which prevents the `click` event from being captured by the Analytics API. This can be used if tracking is explicitly not warranted, or in order to use custom link tracking in specific situations.
26
+
27
+ ## 0.8.7-next.0
28
+
29
+ ### Patch Changes
30
+
31
+ - 4c773ed25c: Change subtitle of Header style to use palette.bursts.fontColor
32
+ - f465b63b7f: Fix an issue where changes related to the `MobileSidebar` prevented scrolling pages. Additionally improve the menu of the `MobileSidebar` to not overlay the `BottomNavigation`.
33
+ - a681cb9c2f: Make linkTarget configurable for MarkdownContent component
34
+
3
35
  ## 0.8.6
4
36
 
5
37
  ### Patch Changes
@@ -45,7 +45,7 @@ const codeModifiers = Object.fromEntries(Object.entries({
45
45
  46: (m) => ({ ...m, background: "cyan" }),
46
46
  47: (m) => ({ ...m, background: "white" }),
47
47
  49: ({ background: _, ...m }) => m
48
- }).map(([code, modifier]) => [`[${code}m`, modifier]));
48
+ }).map(([code, modifier]) => [`\x1B[${code}m`, modifier]));
49
49
  class AnsiLine {
50
50
  constructor(lineNumber = 1, chunks = []) {
51
51
  this.lineNumber = lineNumber;
@@ -552,4 +552,4 @@ function RealLogViewer(props) {
552
552
  }
553
553
 
554
554
  export { RealLogViewer };
555
- //# sourceMappingURL=RealLogViewer-36241415.esm.js.map
555
+ //# sourceMappingURL=RealLogViewer-e0ae55bd.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RealLogViewer-36241415.esm.js","sources":["../../src/components/LogViewer/AnsiProcessor.ts","../../src/components/LogViewer/styles.ts","../../src/components/LogViewer/LogLine.tsx","../../src/components/LogViewer/LogViewerControls.tsx","../../src/components/LogViewer/useLogViewerSearch.tsx","../../src/components/LogViewer/useLogViewerSelection.tsx","../../src/components/LogViewer/RealLogViewer.tsx"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport ansiRegexMaker from 'ansi-regex';\n\nconst ansiRegex = ansiRegexMaker();\nconst newlineRegex = /\\n\\r?/g;\n\n// A mapping of how each escape code changes the modifiers\nconst codeModifiers = Object.fromEntries(\n Object.entries({\n 1: m => ({ ...m, bold: true }),\n 3: m => ({ ...m, italic: true }),\n 4: m => ({ ...m, underline: true }),\n 22: ({ bold: _, ...m }) => m,\n 23: ({ italic: _, ...m }) => m,\n 24: ({ underline: _, ...m }) => m,\n 30: m => ({ ...m, foreground: 'black' }),\n 31: m => ({ ...m, foreground: 'red' }),\n 32: m => ({ ...m, foreground: 'green' }),\n 33: m => ({ ...m, foreground: 'yellow' }),\n 34: m => ({ ...m, foreground: 'blue' }),\n 35: m => ({ ...m, foreground: 'magenta' }),\n 36: m => ({ ...m, foreground: 'cyan' }),\n 37: m => ({ ...m, foreground: 'white' }),\n 39: ({ foreground: _, ...m }) => m,\n 90: m => ({ ...m, foreground: 'grey' }),\n 40: m => ({ ...m, background: 'black' }),\n 41: m => ({ ...m, background: 'red' }),\n 42: m => ({ ...m, background: 'green' }),\n 43: m => ({ ...m, background: 'yellow' }),\n 44: m => ({ ...m, background: 'blue' }),\n 45: m => ({ ...m, background: 'magenta' }),\n 46: m => ({ ...m, background: 'cyan' }),\n 47: m => ({ ...m, background: 'white' }),\n 49: ({ background: _, ...m }) => m,\n } as Record<string, (m: ChunkModifiers) => ChunkModifiers>).map(\n ([code, modifier]) => [`\\x1b[${code}m`, modifier],\n ),\n);\n\nexport type AnsiColor =\n | 'black'\n | 'red'\n | 'green'\n | 'yellow'\n | 'blue'\n | 'magenta'\n | 'cyan'\n | 'white'\n | 'grey';\n\nexport interface ChunkModifiers {\n foreground?: AnsiColor;\n background?: AnsiColor;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n}\n\nexport interface AnsiChunk {\n text: string;\n modifiers: ChunkModifiers;\n}\n\nexport class AnsiLine {\n text: string;\n\n constructor(\n readonly lineNumber: number = 1,\n readonly chunks: AnsiChunk[] = [],\n ) {\n this.text = chunks\n .map(c => c.text)\n .join('')\n .toLocaleLowerCase('en-US');\n }\n\n lastChunk(): AnsiChunk | undefined {\n return this.chunks[this.chunks.length - 1];\n }\n\n replaceLastChunk(newChunks?: AnsiChunk[]) {\n if (newChunks) {\n this.chunks.splice(this.chunks.length - 1, 1, ...newChunks);\n this.text = this.chunks\n .map(c => c.text)\n .join('')\n .toLocaleLowerCase('en-US');\n }\n }\n}\n\nexport class AnsiProcessor {\n private text: string = '';\n private lines: AnsiLine[] = [];\n\n /**\n * Processes a chunk of text while keeping internal state that optimizes\n * subsequent processing that appends to the text.\n */\n process(text: string): AnsiLine[] {\n if (this.text === text) {\n return this.lines;\n }\n\n if (text.startsWith(this.text)) {\n const lastLineIndex = this.lines.length > 0 ? this.lines.length - 1 : 0;\n const lastLine = this.lines[lastLineIndex] ?? new AnsiLine();\n const lastChunk = lastLine.lastChunk();\n\n const newLines = this.processLines(\n (lastChunk?.text ?? '') + text.slice(this.text.length),\n lastChunk?.modifiers,\n lastLine?.lineNumber,\n );\n lastLine.replaceLastChunk(newLines[0]?.chunks);\n\n this.lines[lastLineIndex] = lastLine;\n this.lines.push(...newLines.slice(1));\n } else {\n this.lines = this.processLines(text);\n }\n this.text = text;\n\n return this.lines;\n }\n\n // Split a chunk of text up into lines and process each line individually\n private processLines = (\n text: string,\n modifiers: ChunkModifiers = {},\n startingLineNumber: number = 1,\n ): AnsiLine[] => {\n const lines: AnsiLine[] = [];\n\n let currentModifiers = modifiers;\n let currentLineNumber = startingLineNumber;\n\n let prevIndex = 0;\n newlineRegex.lastIndex = 0;\n for (;;) {\n const match = newlineRegex.exec(text);\n if (!match) {\n const chunks = this.processText(\n text.slice(prevIndex),\n currentModifiers,\n );\n lines.push(new AnsiLine(currentLineNumber, chunks));\n return lines;\n }\n\n const line = text.slice(prevIndex, match.index);\n prevIndex = match.index + match[0].length;\n\n const chunks = this.processText(line, currentModifiers);\n lines.push(new AnsiLine(currentLineNumber, chunks));\n\n // Modifiers that are active in the last chunk are carried over to the next line\n currentModifiers =\n chunks[chunks.length - 1].modifiers ?? currentModifiers;\n currentLineNumber += 1;\n }\n };\n\n // Processing of a one individual text chunk\n private processText = (\n fullText: string,\n modifiers: ChunkModifiers,\n ): AnsiChunk[] => {\n const chunks: AnsiChunk[] = [];\n\n let currentModifiers = modifiers;\n\n let prevIndex = 0;\n ansiRegex.lastIndex = 0;\n for (;;) {\n const match = ansiRegex.exec(fullText);\n if (!match) {\n chunks.push({\n text: fullText.slice(prevIndex),\n modifiers: currentModifiers,\n });\n return chunks;\n }\n\n const text = fullText.slice(prevIndex, match.index);\n chunks.push({ text, modifiers: currentModifiers });\n\n // For every escape code that we encounter we keep track of where the\n // next chunk of text starts, and what modifiers it has\n prevIndex = match.index + match[0].length;\n currentModifiers = this.processCode(match[0], currentModifiers);\n }\n };\n\n private processCode = (\n code: string,\n modifiers: ChunkModifiers,\n ): ChunkModifiers => {\n return codeModifiers[code]?.(modifiers) ?? modifiers;\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { alpha, makeStyles } from '@material-ui/core/styles';\nimport * as colors from '@material-ui/core/colors';\n\nexport const HEADER_SIZE = 40;\n\n/** @public Class keys for overriding LogViewer styles */\nexport type LogViewerClassKey =\n | 'root'\n | 'header'\n | 'log'\n | 'line'\n | 'lineSelected'\n | 'lineCopyButton'\n | 'lineNumber'\n | 'textHighlight'\n | 'textSelectedHighlight'\n | 'modifierBold'\n | 'modifierItalic'\n | 'modifierUnderline'\n | 'modifierForegroundBlack'\n | 'modifierForegroundRed'\n | 'modifierForegroundGreen'\n | 'modifierForegroundYellow'\n | 'modifierForegroundBlue'\n | 'modifierForegroundMagenta'\n | 'modifierForegroundCyan'\n | 'modifierForegroundWhite'\n | 'modifierForegroundGrey'\n | 'modifierBackgroundBlack'\n | 'modifierBackgroundRed'\n | 'modifierBackgroundGreen'\n | 'modifierBackgroundYellow'\n | 'modifierBackgroundBlue'\n | 'modifierBackgroundMagenta'\n | 'modifierBackgroundCyan'\n | 'modifierBackgroundWhite'\n | 'modifierBackgroundGrey';\n\nexport const useStyles = makeStyles(\n theme => ({\n root: {\n background: theme.palette.background.paper,\n },\n header: {\n height: HEADER_SIZE,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n },\n log: {\n fontFamily: '\"Monaco\", monospace',\n fontSize: theme.typography.pxToRem(12),\n },\n line: {\n position: 'relative',\n whiteSpace: 'pre',\n\n '&:hover': {\n background: theme.palette.action.hover,\n },\n },\n lineSelected: {\n background: theme.palette.action.selected,\n\n '&:hover': {\n background: theme.palette.action.selected,\n },\n },\n lineCopyButton: {\n position: 'absolute',\n paddingTop: 0,\n paddingBottom: 0,\n },\n lineNumber: {\n display: 'inline-block',\n textAlign: 'end',\n width: 60,\n marginRight: theme.spacing(1),\n cursor: 'pointer',\n },\n textHighlight: {\n background: alpha(theme.palette.info.main, 0.15),\n },\n textSelectedHighlight: {\n background: alpha(theme.palette.info.main, 0.4),\n },\n modifierBold: {\n fontWeight: theme.typography.fontWeightBold,\n },\n modifierItalic: {\n fontStyle: 'italic',\n },\n modifierUnderline: {\n textDecoration: 'underline',\n },\n modifierForegroundBlack: {\n color: colors.common.black,\n },\n modifierForegroundRed: {\n color: colors.red[500],\n },\n modifierForegroundGreen: {\n color: colors.green[500],\n },\n modifierForegroundYellow: {\n color: colors.yellow[500],\n },\n modifierForegroundBlue: {\n color: colors.blue[500],\n },\n modifierForegroundMagenta: {\n color: colors.purple[500],\n },\n modifierForegroundCyan: {\n color: colors.cyan[500],\n },\n modifierForegroundWhite: {\n color: colors.common.white,\n },\n modifierForegroundGrey: {\n color: colors.grey[500],\n },\n modifierBackgroundBlack: {\n background: colors.common.black,\n },\n modifierBackgroundRed: {\n background: colors.red[500],\n },\n modifierBackgroundGreen: {\n background: colors.green[500],\n },\n modifierBackgroundYellow: {\n background: colors.yellow[500],\n },\n modifierBackgroundBlue: {\n background: colors.blue[500],\n },\n modifierBackgroundMagenta: {\n background: colors.purple[500],\n },\n modifierBackgroundCyan: {\n background: colors.cyan[500],\n },\n modifierBackgroundWhite: {\n background: colors.common.white,\n },\n modifierBackgroundGrey: {\n background: colors.grey[500],\n },\n }),\n { name: 'BackstageLogViewer' },\n);\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { useMemo } from 'react';\nimport { AnsiChunk, AnsiLine, ChunkModifiers } from './AnsiProcessor';\nimport startCase from 'lodash/startCase';\nimport classnames from 'classnames';\nimport { useStyles } from './styles';\n\nexport function getModifierClasses(\n classes: ReturnType<typeof useStyles>,\n modifiers: ChunkModifiers,\n) {\n const classNames = new Array<string>();\n if (modifiers.bold) {\n classNames.push(classes.modifierBold);\n }\n if (modifiers.italic) {\n classNames.push(classes.modifierItalic);\n }\n if (modifiers.underline) {\n classNames.push(classes.modifierUnderline);\n }\n if (modifiers.foreground) {\n const key = `modifierForeground${startCase(\n modifiers.foreground,\n )}` as keyof typeof classes;\n classNames.push(classes[key]);\n }\n if (modifiers.background) {\n const key = `modifierBackground${startCase(\n modifiers.background,\n )}` as keyof typeof classes;\n classNames.push(classes[key]);\n }\n return classNames.length > 0 ? classNames.join(' ') : undefined;\n}\n\nexport function findSearchResults(text: string, searchText: string) {\n if (!searchText || !text.includes(searchText)) {\n return undefined;\n }\n const searchResults = new Array<{ start: number; end: number }>();\n let offset = 0;\n for (;;) {\n const start = text.indexOf(searchText, offset);\n if (start === -1) {\n break;\n }\n const end = start + searchText.length;\n searchResults.push({ start, end });\n offset = end;\n }\n return searchResults;\n}\n\nexport interface HighlightAnsiChunk extends AnsiChunk {\n highlight?: number;\n}\n\nexport function calculateHighlightedChunks(\n line: AnsiLine,\n searchText: string,\n): HighlightAnsiChunk[] {\n const results = findSearchResults(line.text, searchText);\n if (!results) {\n return line.chunks;\n }\n\n const chunks = new Array<HighlightAnsiChunk>();\n\n let lineOffset = 0;\n let resultIndex = 0;\n let result = results[resultIndex];\n for (const chunk of line.chunks) {\n const { text, modifiers } = chunk;\n if (!result || lineOffset + text.length < result.start) {\n chunks.push(chunk);\n lineOffset += text.length;\n continue;\n }\n\n let localOffset = 0;\n while (result) {\n const localStart = Math.max(result.start - lineOffset, 0);\n if (localStart > text.length) {\n break; // The next result is not in this chunk\n }\n\n const localEnd = Math.min(result.end - lineOffset, text.length);\n\n const hasTextBeforeResult = localStart > localOffset;\n if (hasTextBeforeResult) {\n chunks.push({ text: text.slice(localOffset, localStart), modifiers });\n }\n const hasResultText = localEnd > localStart;\n if (hasResultText) {\n chunks.push({\n modifiers,\n highlight: resultIndex,\n text: text.slice(localStart, localEnd),\n });\n }\n\n localOffset = localEnd;\n\n const foundCompleteResult = result.end - lineOffset === localEnd;\n if (foundCompleteResult) {\n resultIndex += 1;\n result = results[resultIndex];\n } else {\n break; // The rest of the result is in the following chunks\n }\n }\n\n const hasTextAfterResult = localOffset < text.length;\n if (hasTextAfterResult) {\n chunks.push({ text: text.slice(localOffset), modifiers });\n }\n\n lineOffset += text.length;\n }\n\n return chunks;\n}\n\nexport interface LogLineProps {\n line: AnsiLine;\n classes: ReturnType<typeof useStyles>;\n searchText: string;\n highlightResultIndex?: number;\n}\n\nexport function LogLine({\n line,\n classes,\n searchText,\n highlightResultIndex,\n}: LogLineProps) {\n const chunks = useMemo(\n () => calculateHighlightedChunks(line, searchText),\n [line, searchText],\n );\n\n const elements = useMemo(\n () =>\n chunks.map(({ text, modifiers, highlight }, index) => (\n <span\n key={index}\n className={classnames(\n getModifierClasses(classes, modifiers),\n highlight !== undefined &&\n (highlight === highlightResultIndex\n ? classes.textSelectedHighlight\n : classes.textHighlight),\n )}\n >\n {text}\n </span>\n )),\n [chunks, highlightResultIndex, classes],\n );\n\n return <>{elements}</>;\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React from 'react';\nimport IconButton from '@material-ui/core/IconButton';\nimport TextField from '@material-ui/core/TextField';\nimport Typography from '@material-ui/core/Typography';\nimport ChevronLeftIcon from '@material-ui/icons/ChevronLeft';\nimport ChevronRightIcon from '@material-ui/icons/ChevronRight';\nimport FilterListIcon from '@material-ui/icons/FilterList';\nimport { LogViewerSearch } from './useLogViewerSearch';\n\nexport interface LogViewerControlsProps extends LogViewerSearch {}\n\nexport function LogViewerControls(props: LogViewerControlsProps) {\n const { resultCount, resultIndexStep, toggleShouldFilter } = props;\n const resultIndex = props.resultIndex ?? 0;\n\n const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n if (event.metaKey || event.ctrlKey || event.altKey) {\n toggleShouldFilter();\n } else {\n resultIndexStep(event.shiftKey);\n }\n }\n };\n\n return (\n <>\n {resultCount !== undefined && (\n <>\n <IconButton size=\"small\" onClick={() => resultIndexStep(true)}>\n <ChevronLeftIcon />\n </IconButton>\n <Typography>\n {Math.min(resultIndex + 1, resultCount)}/{resultCount}\n </Typography>\n <IconButton size=\"small\" onClick={() => resultIndexStep()}>\n <ChevronRightIcon />\n </IconButton>\n </>\n )}\n <TextField\n size=\"small\"\n variant=\"standard\"\n placeholder=\"Search\"\n value={props.searchInput}\n onKeyPress={handleKeyPress}\n onChange={e => props.setSearchInput(e.target.value)}\n />\n <IconButton size=\"small\" onClick={toggleShouldFilter}>\n {props.shouldFilter ? (\n <FilterListIcon color=\"primary\" />\n ) : (\n <FilterListIcon color=\"disabled\" />\n )}\n </IconButton>\n </>\n );\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useMemo, useState } from 'react';\nimport useToggle from 'react-use/lib/useToggle';\nimport { AnsiLine } from './AnsiProcessor';\n\nexport function applySearchFilter(lines: AnsiLine[], searchText: string) {\n if (!searchText) {\n return { lines };\n }\n\n const matchingLines = [];\n const searchResults = [];\n for (const line of lines) {\n if (line.text.includes(searchText)) {\n matchingLines.push(line);\n\n let offset = 0;\n let lineResultIndex = 0;\n for (;;) {\n const start = line.text.indexOf(searchText, offset);\n if (start === -1) {\n break;\n }\n searchResults.push({\n lineNumber: line.lineNumber,\n lineIndex: lineResultIndex++,\n });\n offset = start + searchText.length;\n }\n }\n }\n\n return {\n lines: matchingLines,\n results: searchResults,\n };\n}\n\nexport interface LogViewerSearch {\n lines: AnsiLine[];\n\n searchText: string;\n searchInput: string;\n setSearchInput: (searchInput: string) => void;\n\n shouldFilter: boolean;\n toggleShouldFilter: () => void;\n\n resultCount: number | undefined;\n resultIndex: number | undefined;\n resultIndexStep: (decrement?: boolean) => void;\n\n resultLine: number | undefined;\n resultLineIndex: number | undefined;\n}\n\nexport function useLogViewerSearch(lines: AnsiLine[]): LogViewerSearch {\n const [searchInput, setSearchInput] = useState('');\n const searchText = searchInput.toLocaleLowerCase('en-US');\n\n const [resultIndex, setResultIndex] = useState<number>(0);\n\n const [shouldFilter, toggleShouldFilter] = useToggle(false);\n\n const filter = useMemo(\n () => applySearchFilter(lines, searchText),\n [lines, searchText],\n );\n\n const searchResult = filter.results\n ? filter.results[Math.min(resultIndex, filter.results.length - 1)]\n : undefined;\n const resultCount = filter.results?.length;\n\n const resultIndexStep = (decrement?: boolean) => {\n if (decrement) {\n if (resultCount !== undefined) {\n const next = Math.min(resultIndex - 1, resultCount - 2);\n setResultIndex(next < 0 ? resultCount - 1 : next);\n }\n } else {\n if (resultCount !== undefined) {\n const next = resultIndex + 1;\n setResultIndex(next >= resultCount ? 0 : next);\n }\n }\n };\n\n return {\n lines: shouldFilter ? filter.lines : lines,\n searchText,\n searchInput,\n setSearchInput,\n shouldFilter,\n toggleShouldFilter,\n resultCount,\n resultIndex,\n resultIndexStep,\n resultLine: searchResult?.lineNumber,\n resultLineIndex: searchResult?.lineIndex,\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { errorApiRef, useApi } from '@backstage/core-plugin-api';\nimport { useEffect, useState } from 'react';\nimport useCopyToClipboard from 'react-use/lib/useCopyToClipboard';\nimport { AnsiLine } from './AnsiProcessor';\n\nexport function useLogViewerSelection(lines: AnsiLine[]) {\n const errorApi = useApi(errorApiRef);\n const [sel, setSelection] = useState<{ start: number; end: number }>();\n const start = sel ? Math.min(sel.start, sel.end) : undefined;\n const end = sel ? Math.max(sel.start, sel.end) : undefined;\n\n const [{ error }, copyToClipboard] = useCopyToClipboard();\n\n useEffect(() => {\n if (error) {\n errorApi.post(error);\n }\n }, [error, errorApi]);\n\n return {\n shouldShowButton(line: number) {\n return start === line || end === line;\n },\n isSelected(line: number) {\n if (!sel) {\n return false;\n }\n return start! <= line && line <= end!;\n },\n setSelection(line: number, add: boolean) {\n if (add) {\n setSelection(s =>\n s ? { start: s.start, end: line } : { start: line, end: line },\n );\n } else {\n setSelection(s =>\n s?.start === line && s?.end === line\n ? undefined\n : { start: line, end: line },\n );\n }\n },\n copySelection() {\n if (sel) {\n const copyText = lines\n .slice(Math.min(sel.start, sel.end) - 1, Math.max(sel.start, sel.end))\n .map(l => l.chunks.map(c => c.text).join(''))\n .join('\\n');\n copyToClipboard(copyText);\n setSelection(undefined);\n }\n },\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { useEffect, useMemo, useRef } from 'react';\nimport IconButton from '@material-ui/core/IconButton';\nimport CopyIcon from '@material-ui/icons/FileCopy';\nimport AutoSizer from 'react-virtualized-auto-sizer';\nimport { FixedSizeList } from 'react-window';\nimport { AnsiProcessor } from './AnsiProcessor';\nimport { HEADER_SIZE, useStyles } from './styles';\nimport classnames from 'classnames';\nimport { LogLine } from './LogLine';\nimport { LogViewerControls } from './LogViewerControls';\nimport { useLogViewerSearch } from './useLogViewerSearch';\nimport { useLogViewerSelection } from './useLogViewerSelection';\n\nexport interface RealLogViewerProps {\n text: string;\n classes?: { root?: string };\n}\n\nexport function RealLogViewer(props: RealLogViewerProps) {\n const classes = useStyles({ classes: props.classes });\n const listRef = useRef<FixedSizeList | null>(null);\n\n // The processor keeps state that optimizes appending to the text\n const processor = useMemo(() => new AnsiProcessor(), []);\n const lines = processor.process(props.text);\n\n const search = useLogViewerSearch(lines);\n const selection = useLogViewerSelection(lines);\n\n useEffect(() => {\n if (search.resultLine !== undefined && listRef.current) {\n listRef.current.scrollToItem(search.resultLine - 1, 'center');\n }\n }, [search.resultLine]);\n\n const handleSelectLine = (\n line: number,\n event: { shiftKey: boolean; preventDefault: () => void },\n ) => {\n event.preventDefault();\n selection.setSelection(line, event.shiftKey);\n };\n\n return (\n <AutoSizer>\n {({ height, width }) => (\n <div style={{ width, height }} className={classes.root}>\n <div className={classes.header}>\n <LogViewerControls {...search} />\n </div>\n <FixedSizeList\n ref={listRef}\n className={classes.log}\n height={height - HEADER_SIZE}\n width={width}\n itemData={search.lines}\n itemSize={20}\n itemCount={search.lines.length}\n >\n {({ index, style, data }) => {\n const line = data[index];\n const { lineNumber } = line;\n return (\n <div\n style={{ ...style }}\n className={classnames(classes.line, {\n [classes.lineSelected]: selection.isSelected(lineNumber),\n })}\n >\n {selection.shouldShowButton(lineNumber) && (\n <IconButton\n data-testid=\"copy-button\"\n size=\"small\"\n className={classes.lineCopyButton}\n onClick={() => selection.copySelection()}\n >\n <CopyIcon fontSize=\"inherit\" />\n </IconButton>\n )}\n <a\n role=\"row\"\n target=\"_self\"\n href={`#line-${lineNumber}`}\n className={classes.lineNumber}\n onClick={event => handleSelectLine(lineNumber, event)}\n onKeyPress={event => handleSelectLine(lineNumber, event)}\n >\n {lineNumber}\n </a>\n <LogLine\n line={line}\n classes={classes}\n searchText={search.searchText}\n highlightResultIndex={\n search.resultLine === lineNumber\n ? search.resultLineIndex\n : undefined\n }\n />\n </div>\n );\n }}\n </FixedSizeList>\n </div>\n )}\n </AutoSizer>\n );\n}\n"],"names":["classnames","ChevronRightIcon","FilterListIcon"],"mappings":"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"}
1
+ {"version":3,"file":"RealLogViewer-e0ae55bd.esm.js","sources":["../../src/components/LogViewer/AnsiProcessor.ts","../../src/components/LogViewer/styles.ts","../../src/components/LogViewer/LogLine.tsx","../../src/components/LogViewer/LogViewerControls.tsx","../../src/components/LogViewer/useLogViewerSearch.tsx","../../src/components/LogViewer/useLogViewerSelection.tsx","../../src/components/LogViewer/RealLogViewer.tsx"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport ansiRegexMaker from 'ansi-regex';\n\nconst ansiRegex = ansiRegexMaker();\nconst newlineRegex = /\\n\\r?/g;\n\n// A mapping of how each escape code changes the modifiers\nconst codeModifiers = Object.fromEntries(\n Object.entries({\n 1: m => ({ ...m, bold: true }),\n 3: m => ({ ...m, italic: true }),\n 4: m => ({ ...m, underline: true }),\n 22: ({ bold: _, ...m }) => m,\n 23: ({ italic: _, ...m }) => m,\n 24: ({ underline: _, ...m }) => m,\n 30: m => ({ ...m, foreground: 'black' }),\n 31: m => ({ ...m, foreground: 'red' }),\n 32: m => ({ ...m, foreground: 'green' }),\n 33: m => ({ ...m, foreground: 'yellow' }),\n 34: m => ({ ...m, foreground: 'blue' }),\n 35: m => ({ ...m, foreground: 'magenta' }),\n 36: m => ({ ...m, foreground: 'cyan' }),\n 37: m => ({ ...m, foreground: 'white' }),\n 39: ({ foreground: _, ...m }) => m,\n 90: m => ({ ...m, foreground: 'grey' }),\n 40: m => ({ ...m, background: 'black' }),\n 41: m => ({ ...m, background: 'red' }),\n 42: m => ({ ...m, background: 'green' }),\n 43: m => ({ ...m, background: 'yellow' }),\n 44: m => ({ ...m, background: 'blue' }),\n 45: m => ({ ...m, background: 'magenta' }),\n 46: m => ({ ...m, background: 'cyan' }),\n 47: m => ({ ...m, background: 'white' }),\n 49: ({ background: _, ...m }) => m,\n } as Record<string, (m: ChunkModifiers) => ChunkModifiers>).map(\n ([code, modifier]) => [`\\x1b[${code}m`, modifier],\n ),\n);\n\nexport type AnsiColor =\n | 'black'\n | 'red'\n | 'green'\n | 'yellow'\n | 'blue'\n | 'magenta'\n | 'cyan'\n | 'white'\n | 'grey';\n\nexport interface ChunkModifiers {\n foreground?: AnsiColor;\n background?: AnsiColor;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n}\n\nexport interface AnsiChunk {\n text: string;\n modifiers: ChunkModifiers;\n}\n\nexport class AnsiLine {\n text: string;\n\n constructor(\n readonly lineNumber: number = 1,\n readonly chunks: AnsiChunk[] = [],\n ) {\n this.text = chunks\n .map(c => c.text)\n .join('')\n .toLocaleLowerCase('en-US');\n }\n\n lastChunk(): AnsiChunk | undefined {\n return this.chunks[this.chunks.length - 1];\n }\n\n replaceLastChunk(newChunks?: AnsiChunk[]) {\n if (newChunks) {\n this.chunks.splice(this.chunks.length - 1, 1, ...newChunks);\n this.text = this.chunks\n .map(c => c.text)\n .join('')\n .toLocaleLowerCase('en-US');\n }\n }\n}\n\nexport class AnsiProcessor {\n private text: string = '';\n private lines: AnsiLine[] = [];\n\n /**\n * Processes a chunk of text while keeping internal state that optimizes\n * subsequent processing that appends to the text.\n */\n process(text: string): AnsiLine[] {\n if (this.text === text) {\n return this.lines;\n }\n\n if (text.startsWith(this.text)) {\n const lastLineIndex = this.lines.length > 0 ? this.lines.length - 1 : 0;\n const lastLine = this.lines[lastLineIndex] ?? new AnsiLine();\n const lastChunk = lastLine.lastChunk();\n\n const newLines = this.processLines(\n (lastChunk?.text ?? '') + text.slice(this.text.length),\n lastChunk?.modifiers,\n lastLine?.lineNumber,\n );\n lastLine.replaceLastChunk(newLines[0]?.chunks);\n\n this.lines[lastLineIndex] = lastLine;\n this.lines.push(...newLines.slice(1));\n } else {\n this.lines = this.processLines(text);\n }\n this.text = text;\n\n return this.lines;\n }\n\n // Split a chunk of text up into lines and process each line individually\n private processLines = (\n text: string,\n modifiers: ChunkModifiers = {},\n startingLineNumber: number = 1,\n ): AnsiLine[] => {\n const lines: AnsiLine[] = [];\n\n let currentModifiers = modifiers;\n let currentLineNumber = startingLineNumber;\n\n let prevIndex = 0;\n newlineRegex.lastIndex = 0;\n for (;;) {\n const match = newlineRegex.exec(text);\n if (!match) {\n const chunks = this.processText(\n text.slice(prevIndex),\n currentModifiers,\n );\n lines.push(new AnsiLine(currentLineNumber, chunks));\n return lines;\n }\n\n const line = text.slice(prevIndex, match.index);\n prevIndex = match.index + match[0].length;\n\n const chunks = this.processText(line, currentModifiers);\n lines.push(new AnsiLine(currentLineNumber, chunks));\n\n // Modifiers that are active in the last chunk are carried over to the next line\n currentModifiers =\n chunks[chunks.length - 1].modifiers ?? currentModifiers;\n currentLineNumber += 1;\n }\n };\n\n // Processing of a one individual text chunk\n private processText = (\n fullText: string,\n modifiers: ChunkModifiers,\n ): AnsiChunk[] => {\n const chunks: AnsiChunk[] = [];\n\n let currentModifiers = modifiers;\n\n let prevIndex = 0;\n ansiRegex.lastIndex = 0;\n for (;;) {\n const match = ansiRegex.exec(fullText);\n if (!match) {\n chunks.push({\n text: fullText.slice(prevIndex),\n modifiers: currentModifiers,\n });\n return chunks;\n }\n\n const text = fullText.slice(prevIndex, match.index);\n chunks.push({ text, modifiers: currentModifiers });\n\n // For every escape code that we encounter we keep track of where the\n // next chunk of text starts, and what modifiers it has\n prevIndex = match.index + match[0].length;\n currentModifiers = this.processCode(match[0], currentModifiers);\n }\n };\n\n private processCode = (\n code: string,\n modifiers: ChunkModifiers,\n ): ChunkModifiers => {\n return codeModifiers[code]?.(modifiers) ?? modifiers;\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { alpha, makeStyles } from '@material-ui/core/styles';\nimport * as colors from '@material-ui/core/colors';\n\nexport const HEADER_SIZE = 40;\n\n/** @public Class keys for overriding LogViewer styles */\nexport type LogViewerClassKey =\n | 'root'\n | 'header'\n | 'log'\n | 'line'\n | 'lineSelected'\n | 'lineCopyButton'\n | 'lineNumber'\n | 'textHighlight'\n | 'textSelectedHighlight'\n | 'modifierBold'\n | 'modifierItalic'\n | 'modifierUnderline'\n | 'modifierForegroundBlack'\n | 'modifierForegroundRed'\n | 'modifierForegroundGreen'\n | 'modifierForegroundYellow'\n | 'modifierForegroundBlue'\n | 'modifierForegroundMagenta'\n | 'modifierForegroundCyan'\n | 'modifierForegroundWhite'\n | 'modifierForegroundGrey'\n | 'modifierBackgroundBlack'\n | 'modifierBackgroundRed'\n | 'modifierBackgroundGreen'\n | 'modifierBackgroundYellow'\n | 'modifierBackgroundBlue'\n | 'modifierBackgroundMagenta'\n | 'modifierBackgroundCyan'\n | 'modifierBackgroundWhite'\n | 'modifierBackgroundGrey';\n\nexport const useStyles = makeStyles(\n theme => ({\n root: {\n background: theme.palette.background.paper,\n },\n header: {\n height: HEADER_SIZE,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n },\n log: {\n fontFamily: '\"Monaco\", monospace',\n fontSize: theme.typography.pxToRem(12),\n },\n line: {\n position: 'relative',\n whiteSpace: 'pre',\n\n '&:hover': {\n background: theme.palette.action.hover,\n },\n },\n lineSelected: {\n background: theme.palette.action.selected,\n\n '&:hover': {\n background: theme.palette.action.selected,\n },\n },\n lineCopyButton: {\n position: 'absolute',\n paddingTop: 0,\n paddingBottom: 0,\n },\n lineNumber: {\n display: 'inline-block',\n textAlign: 'end',\n width: 60,\n marginRight: theme.spacing(1),\n cursor: 'pointer',\n },\n textHighlight: {\n background: alpha(theme.palette.info.main, 0.15),\n },\n textSelectedHighlight: {\n background: alpha(theme.palette.info.main, 0.4),\n },\n modifierBold: {\n fontWeight: theme.typography.fontWeightBold,\n },\n modifierItalic: {\n fontStyle: 'italic',\n },\n modifierUnderline: {\n textDecoration: 'underline',\n },\n modifierForegroundBlack: {\n color: colors.common.black,\n },\n modifierForegroundRed: {\n color: colors.red[500],\n },\n modifierForegroundGreen: {\n color: colors.green[500],\n },\n modifierForegroundYellow: {\n color: colors.yellow[500],\n },\n modifierForegroundBlue: {\n color: colors.blue[500],\n },\n modifierForegroundMagenta: {\n color: colors.purple[500],\n },\n modifierForegroundCyan: {\n color: colors.cyan[500],\n },\n modifierForegroundWhite: {\n color: colors.common.white,\n },\n modifierForegroundGrey: {\n color: colors.grey[500],\n },\n modifierBackgroundBlack: {\n background: colors.common.black,\n },\n modifierBackgroundRed: {\n background: colors.red[500],\n },\n modifierBackgroundGreen: {\n background: colors.green[500],\n },\n modifierBackgroundYellow: {\n background: colors.yellow[500],\n },\n modifierBackgroundBlue: {\n background: colors.blue[500],\n },\n modifierBackgroundMagenta: {\n background: colors.purple[500],\n },\n modifierBackgroundCyan: {\n background: colors.cyan[500],\n },\n modifierBackgroundWhite: {\n background: colors.common.white,\n },\n modifierBackgroundGrey: {\n background: colors.grey[500],\n },\n }),\n { name: 'BackstageLogViewer' },\n);\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { useMemo } from 'react';\nimport { AnsiChunk, AnsiLine, ChunkModifiers } from './AnsiProcessor';\nimport startCase from 'lodash/startCase';\nimport classnames from 'classnames';\nimport { useStyles } from './styles';\n\nexport function getModifierClasses(\n classes: ReturnType<typeof useStyles>,\n modifiers: ChunkModifiers,\n) {\n const classNames = new Array<string>();\n if (modifiers.bold) {\n classNames.push(classes.modifierBold);\n }\n if (modifiers.italic) {\n classNames.push(classes.modifierItalic);\n }\n if (modifiers.underline) {\n classNames.push(classes.modifierUnderline);\n }\n if (modifiers.foreground) {\n const key = `modifierForeground${startCase(\n modifiers.foreground,\n )}` as keyof typeof classes;\n classNames.push(classes[key]);\n }\n if (modifiers.background) {\n const key = `modifierBackground${startCase(\n modifiers.background,\n )}` as keyof typeof classes;\n classNames.push(classes[key]);\n }\n return classNames.length > 0 ? classNames.join(' ') : undefined;\n}\n\nexport function findSearchResults(text: string, searchText: string) {\n if (!searchText || !text.includes(searchText)) {\n return undefined;\n }\n const searchResults = new Array<{ start: number; end: number }>();\n let offset = 0;\n for (;;) {\n const start = text.indexOf(searchText, offset);\n if (start === -1) {\n break;\n }\n const end = start + searchText.length;\n searchResults.push({ start, end });\n offset = end;\n }\n return searchResults;\n}\n\nexport interface HighlightAnsiChunk extends AnsiChunk {\n highlight?: number;\n}\n\nexport function calculateHighlightedChunks(\n line: AnsiLine,\n searchText: string,\n): HighlightAnsiChunk[] {\n const results = findSearchResults(line.text, searchText);\n if (!results) {\n return line.chunks;\n }\n\n const chunks = new Array<HighlightAnsiChunk>();\n\n let lineOffset = 0;\n let resultIndex = 0;\n let result = results[resultIndex];\n for (const chunk of line.chunks) {\n const { text, modifiers } = chunk;\n if (!result || lineOffset + text.length < result.start) {\n chunks.push(chunk);\n lineOffset += text.length;\n continue;\n }\n\n let localOffset = 0;\n while (result) {\n const localStart = Math.max(result.start - lineOffset, 0);\n if (localStart > text.length) {\n break; // The next result is not in this chunk\n }\n\n const localEnd = Math.min(result.end - lineOffset, text.length);\n\n const hasTextBeforeResult = localStart > localOffset;\n if (hasTextBeforeResult) {\n chunks.push({ text: text.slice(localOffset, localStart), modifiers });\n }\n const hasResultText = localEnd > localStart;\n if (hasResultText) {\n chunks.push({\n modifiers,\n highlight: resultIndex,\n text: text.slice(localStart, localEnd),\n });\n }\n\n localOffset = localEnd;\n\n const foundCompleteResult = result.end - lineOffset === localEnd;\n if (foundCompleteResult) {\n resultIndex += 1;\n result = results[resultIndex];\n } else {\n break; // The rest of the result is in the following chunks\n }\n }\n\n const hasTextAfterResult = localOffset < text.length;\n if (hasTextAfterResult) {\n chunks.push({ text: text.slice(localOffset), modifiers });\n }\n\n lineOffset += text.length;\n }\n\n return chunks;\n}\n\nexport interface LogLineProps {\n line: AnsiLine;\n classes: ReturnType<typeof useStyles>;\n searchText: string;\n highlightResultIndex?: number;\n}\n\nexport function LogLine({\n line,\n classes,\n searchText,\n highlightResultIndex,\n}: LogLineProps) {\n const chunks = useMemo(\n () => calculateHighlightedChunks(line, searchText),\n [line, searchText],\n );\n\n const elements = useMemo(\n () =>\n chunks.map(({ text, modifiers, highlight }, index) => (\n <span\n key={index}\n className={classnames(\n getModifierClasses(classes, modifiers),\n highlight !== undefined &&\n (highlight === highlightResultIndex\n ? classes.textSelectedHighlight\n : classes.textHighlight),\n )}\n >\n {text}\n </span>\n )),\n [chunks, highlightResultIndex, classes],\n );\n\n return <>{elements}</>;\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React from 'react';\nimport IconButton from '@material-ui/core/IconButton';\nimport TextField from '@material-ui/core/TextField';\nimport Typography from '@material-ui/core/Typography';\nimport ChevronLeftIcon from '@material-ui/icons/ChevronLeft';\nimport ChevronRightIcon from '@material-ui/icons/ChevronRight';\nimport FilterListIcon from '@material-ui/icons/FilterList';\nimport { LogViewerSearch } from './useLogViewerSearch';\n\nexport interface LogViewerControlsProps extends LogViewerSearch {}\n\nexport function LogViewerControls(props: LogViewerControlsProps) {\n const { resultCount, resultIndexStep, toggleShouldFilter } = props;\n const resultIndex = props.resultIndex ?? 0;\n\n const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n if (event.metaKey || event.ctrlKey || event.altKey) {\n toggleShouldFilter();\n } else {\n resultIndexStep(event.shiftKey);\n }\n }\n };\n\n return (\n <>\n {resultCount !== undefined && (\n <>\n <IconButton size=\"small\" onClick={() => resultIndexStep(true)}>\n <ChevronLeftIcon />\n </IconButton>\n <Typography>\n {Math.min(resultIndex + 1, resultCount)}/{resultCount}\n </Typography>\n <IconButton size=\"small\" onClick={() => resultIndexStep()}>\n <ChevronRightIcon />\n </IconButton>\n </>\n )}\n <TextField\n size=\"small\"\n variant=\"standard\"\n placeholder=\"Search\"\n value={props.searchInput}\n onKeyPress={handleKeyPress}\n onChange={e => props.setSearchInput(e.target.value)}\n />\n <IconButton size=\"small\" onClick={toggleShouldFilter}>\n {props.shouldFilter ? (\n <FilterListIcon color=\"primary\" />\n ) : (\n <FilterListIcon color=\"disabled\" />\n )}\n </IconButton>\n </>\n );\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useMemo, useState } from 'react';\nimport useToggle from 'react-use/lib/useToggle';\nimport { AnsiLine } from './AnsiProcessor';\n\nexport function applySearchFilter(lines: AnsiLine[], searchText: string) {\n if (!searchText) {\n return { lines };\n }\n\n const matchingLines = [];\n const searchResults = [];\n for (const line of lines) {\n if (line.text.includes(searchText)) {\n matchingLines.push(line);\n\n let offset = 0;\n let lineResultIndex = 0;\n for (;;) {\n const start = line.text.indexOf(searchText, offset);\n if (start === -1) {\n break;\n }\n searchResults.push({\n lineNumber: line.lineNumber,\n lineIndex: lineResultIndex++,\n });\n offset = start + searchText.length;\n }\n }\n }\n\n return {\n lines: matchingLines,\n results: searchResults,\n };\n}\n\nexport interface LogViewerSearch {\n lines: AnsiLine[];\n\n searchText: string;\n searchInput: string;\n setSearchInput: (searchInput: string) => void;\n\n shouldFilter: boolean;\n toggleShouldFilter: () => void;\n\n resultCount: number | undefined;\n resultIndex: number | undefined;\n resultIndexStep: (decrement?: boolean) => void;\n\n resultLine: number | undefined;\n resultLineIndex: number | undefined;\n}\n\nexport function useLogViewerSearch(lines: AnsiLine[]): LogViewerSearch {\n const [searchInput, setSearchInput] = useState('');\n const searchText = searchInput.toLocaleLowerCase('en-US');\n\n const [resultIndex, setResultIndex] = useState<number>(0);\n\n const [shouldFilter, toggleShouldFilter] = useToggle(false);\n\n const filter = useMemo(\n () => applySearchFilter(lines, searchText),\n [lines, searchText],\n );\n\n const searchResult = filter.results\n ? filter.results[Math.min(resultIndex, filter.results.length - 1)]\n : undefined;\n const resultCount = filter.results?.length;\n\n const resultIndexStep = (decrement?: boolean) => {\n if (decrement) {\n if (resultCount !== undefined) {\n const next = Math.min(resultIndex - 1, resultCount - 2);\n setResultIndex(next < 0 ? resultCount - 1 : next);\n }\n } else {\n if (resultCount !== undefined) {\n const next = resultIndex + 1;\n setResultIndex(next >= resultCount ? 0 : next);\n }\n }\n };\n\n return {\n lines: shouldFilter ? filter.lines : lines,\n searchText,\n searchInput,\n setSearchInput,\n shouldFilter,\n toggleShouldFilter,\n resultCount,\n resultIndex,\n resultIndexStep,\n resultLine: searchResult?.lineNumber,\n resultLineIndex: searchResult?.lineIndex,\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { errorApiRef, useApi } from '@backstage/core-plugin-api';\nimport { useEffect, useState } from 'react';\nimport useCopyToClipboard from 'react-use/lib/useCopyToClipboard';\nimport { AnsiLine } from './AnsiProcessor';\n\nexport function useLogViewerSelection(lines: AnsiLine[]) {\n const errorApi = useApi(errorApiRef);\n const [sel, setSelection] = useState<{ start: number; end: number }>();\n const start = sel ? Math.min(sel.start, sel.end) : undefined;\n const end = sel ? Math.max(sel.start, sel.end) : undefined;\n\n const [{ error }, copyToClipboard] = useCopyToClipboard();\n\n useEffect(() => {\n if (error) {\n errorApi.post(error);\n }\n }, [error, errorApi]);\n\n return {\n shouldShowButton(line: number) {\n return start === line || end === line;\n },\n isSelected(line: number) {\n if (!sel) {\n return false;\n }\n return start! <= line && line <= end!;\n },\n setSelection(line: number, add: boolean) {\n if (add) {\n setSelection(s =>\n s ? { start: s.start, end: line } : { start: line, end: line },\n );\n } else {\n setSelection(s =>\n s?.start === line && s?.end === line\n ? undefined\n : { start: line, end: line },\n );\n }\n },\n copySelection() {\n if (sel) {\n const copyText = lines\n .slice(Math.min(sel.start, sel.end) - 1, Math.max(sel.start, sel.end))\n .map(l => l.chunks.map(c => c.text).join(''))\n .join('\\n');\n copyToClipboard(copyText);\n setSelection(undefined);\n }\n },\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { useEffect, useMemo, useRef } from 'react';\nimport IconButton from '@material-ui/core/IconButton';\nimport CopyIcon from '@material-ui/icons/FileCopy';\nimport AutoSizer from 'react-virtualized-auto-sizer';\nimport { FixedSizeList } from 'react-window';\nimport { AnsiProcessor } from './AnsiProcessor';\nimport { HEADER_SIZE, useStyles } from './styles';\nimport classnames from 'classnames';\nimport { LogLine } from './LogLine';\nimport { LogViewerControls } from './LogViewerControls';\nimport { useLogViewerSearch } from './useLogViewerSearch';\nimport { useLogViewerSelection } from './useLogViewerSelection';\n\nexport interface RealLogViewerProps {\n text: string;\n classes?: { root?: string };\n}\n\nexport function RealLogViewer(props: RealLogViewerProps) {\n const classes = useStyles({ classes: props.classes });\n const listRef = useRef<FixedSizeList | null>(null);\n\n // The processor keeps state that optimizes appending to the text\n const processor = useMemo(() => new AnsiProcessor(), []);\n const lines = processor.process(props.text);\n\n const search = useLogViewerSearch(lines);\n const selection = useLogViewerSelection(lines);\n\n useEffect(() => {\n if (search.resultLine !== undefined && listRef.current) {\n listRef.current.scrollToItem(search.resultLine - 1, 'center');\n }\n }, [search.resultLine]);\n\n const handleSelectLine = (\n line: number,\n event: { shiftKey: boolean; preventDefault: () => void },\n ) => {\n event.preventDefault();\n selection.setSelection(line, event.shiftKey);\n };\n\n return (\n <AutoSizer>\n {({ height, width }) => (\n <div style={{ width, height }} className={classes.root}>\n <div className={classes.header}>\n <LogViewerControls {...search} />\n </div>\n <FixedSizeList\n ref={listRef}\n className={classes.log}\n height={height - HEADER_SIZE}\n width={width}\n itemData={search.lines}\n itemSize={20}\n itemCount={search.lines.length}\n >\n {({ index, style, data }) => {\n const line = data[index];\n const { lineNumber } = line;\n return (\n <div\n style={{ ...style }}\n className={classnames(classes.line, {\n [classes.lineSelected]: selection.isSelected(lineNumber),\n })}\n >\n {selection.shouldShowButton(lineNumber) && (\n <IconButton\n data-testid=\"copy-button\"\n size=\"small\"\n className={classes.lineCopyButton}\n onClick={() => selection.copySelection()}\n >\n <CopyIcon fontSize=\"inherit\" />\n </IconButton>\n )}\n <a\n role=\"row\"\n target=\"_self\"\n href={`#line-${lineNumber}`}\n className={classes.lineNumber}\n onClick={event => handleSelectLine(lineNumber, event)}\n onKeyPress={event => handleSelectLine(lineNumber, event)}\n >\n {lineNumber}\n </a>\n <LogLine\n line={line}\n classes={classes}\n searchText={search.searchText}\n highlightResultIndex={\n search.resultLine === lineNumber\n ? search.resultLineIndex\n : undefined\n }\n />\n </div>\n );\n }}\n </FixedSizeList>\n </div>\n )}\n </AutoSizer>\n );\n}\n"],"names":["classnames","ChevronRightIcon","FilterListIcon"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ import { ButtonProps as ButtonProps$1 } from '@material-ui/core/Button';
5
5
  import { LinkProps as LinkProps$1 } from '@material-ui/core/Link';
6
6
  import { LinkProps as LinkProps$2, NavLinkProps } from 'react-router-dom';
7
7
  import CSS from 'csstype';
8
+ import { Options } from 'react-markdown';
8
9
  import { TooltipProps } from '@material-ui/core/Tooltip';
9
10
  import { TextTruncateProps } from 'react-text-truncate';
10
11
  import { LinearProgressProps } from '@material-ui/core/LinearProgress';
@@ -65,13 +66,14 @@ declare function Avatar(props: AvatarProps): JSX.Element;
65
66
 
66
67
  declare type LinkProps = LinkProps$1 & LinkProps$2 & {
67
68
  component?: ElementType<any>;
69
+ noTrack?: boolean;
68
70
  };
69
71
  /**
70
72
  * Thin wrapper on top of material-ui's Link component, which...
71
73
  * - Makes the Link use react-router
72
74
  * - Captures Link clicks as analytics events.
73
75
  */
74
- declare const ActualLink: React__default.ForwardRefExoticComponent<Pick<LinkProps, "replace" | "type" | "key" | "id" | "media" | "state" | "color" | "display" | "translate" | "hidden" | "dir" | "slot" | "style" | "title" | "target" | "accessKey" | "draggable" | "lang" | "className" | "prefix" | "children" | "contentEditable" | "inputMode" | "tabIndex" | "underline" | "download" | "href" | "hrefLang" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "contextMenu" | "placeholder" | "spellCheck" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "to" | "rel" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "ping" | "referrerPolicy" | "align" | "variant" | "component" | "classes" | "innerRef" | "noWrap" | "gutterBottom" | "paragraph" | "variantMapping" | "TypographyClasses"> & React__default.RefAttributes<any>>;
76
+ declare const ActualLink: React__default.ForwardRefExoticComponent<Pick<LinkProps, "replace" | "type" | "key" | "id" | "media" | "state" | "color" | "display" | "translate" | "hidden" | "dir" | "slot" | "style" | "title" | "target" | "accessKey" | "draggable" | "lang" | "className" | "prefix" | "children" | "contentEditable" | "inputMode" | "tabIndex" | "underline" | "download" | "href" | "hrefLang" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "contextMenu" | "placeholder" | "spellCheck" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "to" | "rel" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "ping" | "referrerPolicy" | "align" | "variant" | "component" | "classes" | "innerRef" | "noWrap" | "gutterBottom" | "paragraph" | "variantMapping" | "noTrack" | "TypographyClasses"> & React__default.RefAttributes<any>>;
75
77
 
76
78
  /**
77
79
  * Properties for {@link Button}
@@ -669,6 +671,7 @@ declare type MarkdownContentClassKey = 'markdown';
669
671
  declare type Props$b = {
670
672
  content: string;
671
673
  dialect?: 'gfm' | 'common-mark';
674
+ linkTarget?: Options['linkTarget'];
672
675
  };
673
676
  /**
674
677
  * MarkdownContent
@@ -763,6 +766,7 @@ declare type Props$9 = {
763
766
  children?: ReactNode;
764
767
  headerStyle?: object;
765
768
  headerProps?: CardHeaderProps;
769
+ icon?: ReactNode;
766
770
  action?: ReactNode;
767
771
  actionsClassName?: string;
768
772
  actions?: ReactNode;
@@ -781,7 +785,7 @@ declare type Props$9 = {
781
785
  declare function InfoCard(props: Props$9): JSX.Element;
782
786
 
783
787
  /** @public */
784
- declare type GaugeClassKey = 'root' | 'overlay' | 'circle' | 'colorUnknown';
788
+ declare type GaugeClassKey = 'root' | 'overlay' | 'description' | 'circle' | 'colorUnknown';
785
789
  /** @public */
786
790
  declare type GaugeProps = {
787
791
  value: number;
@@ -789,6 +793,7 @@ declare type GaugeProps = {
789
793
  inverse?: boolean;
790
794
  unit?: string;
791
795
  max?: number;
796
+ description?: ReactNode;
792
797
  getColor?: GaugePropsGetColor;
793
798
  };
794
799
  /** @public */
@@ -814,6 +819,8 @@ declare type Props$8 = {
814
819
  variant?: InfoCardVariants;
815
820
  /** Progress in % specified as decimal, e.g. "0.23" */
816
821
  progress: number;
822
+ description?: ReactNode;
823
+ icon?: ReactNode;
817
824
  inverse?: boolean;
818
825
  deepLink?: BottomLinkProps;
819
826
  getColor?: GaugePropsGetColor;
@@ -1142,7 +1149,8 @@ declare function ContentHeader(props: PropsWithChildren<ContentHeaderProps>): JS
1142
1149
  interface IErrorPageProps {
1143
1150
  status: string;
1144
1151
  statusMessage: string;
1145
- additionalInfo?: string;
1152
+ additionalInfo?: React__default.ReactNode;
1153
+ supportUrl?: string;
1146
1154
  }
1147
1155
  /** @public */
1148
1156
  declare type ErrorPageClassKey = 'container' | 'title' | 'subtitle';
@@ -1332,8 +1340,9 @@ declare function ItemCardHeader(props: ItemCardHeaderProps): JSX.Element;
1332
1340
  declare type PageClassKey = 'root';
1333
1341
  declare type Props$3 = {
1334
1342
  themeId: string;
1343
+ children?: React__default.ReactNode;
1335
1344
  };
1336
- declare function Page(props: PropsWithChildren<Props$3>): JSX.Element;
1345
+ declare function Page(props: Props$3): JSX.Element;
1337
1346
 
1338
1347
  declare type PageWithHeaderProps = ComponentProps<typeof Header> & {
1339
1348
  themeId: string;
package/dist/index.esm.js CHANGED
@@ -3,7 +3,7 @@ import Snackbar from '@material-ui/core/Snackbar';
3
3
  import IconButton from '@material-ui/core/IconButton';
4
4
  import CloseIcon from '@material-ui/icons/Close';
5
5
  import { Alert } from '@material-ui/lab';
6
- import { useApi, alertApiRef, useAnalytics, errorApiRef, storageApiRef, useApp, oauthRequestApiRef, configApiRef, useElementFilter, attachComponentData, discoveryApiRef, useApiHolder } from '@backstage/core-plugin-api';
6
+ import { useApi, alertApiRef, useAnalytics, errorApiRef, storageApiRef, useApp, oauthRequestApiRef, useApiHolder, configApiRef, useElementFilter, attachComponentData, discoveryApiRef } from '@backstage/core-plugin-api';
7
7
  import pluralize from 'pluralize';
8
8
  import { makeStyles, createStyles, useTheme, darken, lighten, withStyles, styled, ThemeProvider } from '@material-ui/core/styles';
9
9
  import MaterialAvatar from '@material-ui/core/Avatar';
@@ -220,7 +220,7 @@ const getNodeText = (node) => {
220
220
  }
221
221
  return "";
222
222
  };
223
- const ActualLink = React.forwardRef(({ onClick, ...props }, ref) => {
223
+ const ActualLink = React.forwardRef(({ onClick, noTrack, ...props }, ref) => {
224
224
  const analytics = useAnalytics();
225
225
  const to = String(props.to);
226
226
  const linkText = getNodeText(props.children) || to;
@@ -228,7 +228,9 @@ const ActualLink = React.forwardRef(({ onClick, ...props }, ref) => {
228
228
  const newWindow = external && !!/^https?:/.exec(to);
229
229
  const handleClick = (event) => {
230
230
  onClick == null ? void 0 : onClick(event);
231
- analytics.captureEvent("click", linkText, { attributes: { to } });
231
+ if (!noTrack) {
232
+ analytics.captureEvent("click", linkText, { attributes: { to } });
233
+ }
232
234
  };
233
235
  return external ? /* @__PURE__ */ React.createElement(Link, {
234
236
  ref,
@@ -1613,7 +1615,7 @@ function Lifecycle(props) {
1613
1615
  }, alpha ? "Alpha" : "Beta");
1614
1616
  }
1615
1617
 
1616
- const RealLogViewer = lazy(() => import('./esm/RealLogViewer-36241415.esm.js').then((m) => ({ default: m.RealLogViewer })));
1618
+ const RealLogViewer = lazy(() => import('./esm/RealLogViewer-e0ae55bd.esm.js').then((m) => ({ default: m.RealLogViewer })));
1617
1619
  function LogViewer(props) {
1618
1620
  const { Progress } = useApp().getComponents();
1619
1621
  return /* @__PURE__ */ React.createElement(Suspense, {
@@ -1673,13 +1675,14 @@ const components = {
1673
1675
  }
1674
1676
  };
1675
1677
  function MarkdownContent(props) {
1676
- const { content, dialect = "gfm" } = props;
1678
+ const { content, dialect = "gfm", linkTarget } = props;
1677
1679
  const classes = useStyles$C();
1678
1680
  return /* @__PURE__ */ React.createElement(ReactMarkdown, {
1679
1681
  remarkPlugins: dialect === "gfm" ? [gfm] : [],
1680
1682
  className: classes.markdown,
1681
1683
  children: content,
1682
- components
1684
+ components,
1685
+ linkTarget
1683
1686
  });
1684
1687
  }
1685
1688
 
@@ -1897,7 +1900,10 @@ const useStyles$y = makeStyles((theme) => ({
1897
1900
  },
1898
1901
  headerAvatar: {},
1899
1902
  headerAction: {},
1900
- headerContent: {}
1903
+ headerContent: {},
1904
+ subheader: {
1905
+ display: "flex"
1906
+ }
1901
1907
  }), { name: "BackstageInfoCard" });
1902
1908
  const CardActionsTopRight = withStyles((theme) => ({
1903
1909
  root: {
@@ -1945,6 +1951,7 @@ function InfoCard(props) {
1945
1951
  children,
1946
1952
  headerStyle,
1947
1953
  headerProps,
1954
+ icon,
1948
1955
  action,
1949
1956
  actionsClassName,
1950
1957
  actions,
@@ -1970,6 +1977,13 @@ function InfoCard(props) {
1970
1977
  };
1971
1978
  });
1972
1979
  }
1980
+ const cardSubTitle = () => {
1981
+ return /* @__PURE__ */ React.createElement("div", {
1982
+ className: classes.headerSubheader
1983
+ }, subheader && /* @__PURE__ */ React.createElement("div", {
1984
+ className: classes.subheader
1985
+ }, subheader), icon);
1986
+ };
1973
1987
  const errProps = errorBoundaryProps || (slackChannel ? { slackChannel } : {});
1974
1988
  return /* @__PURE__ */ React.createElement(Card, {
1975
1989
  style: calculatedStyle,
@@ -1986,7 +2000,7 @@ function InfoCard(props) {
1986
2000
  content: classes.headerContent
1987
2001
  },
1988
2002
  title,
1989
- subheader,
2003
+ subheader: cardSubTitle(),
1990
2004
  action,
1991
2005
  style: { ...headerStyle },
1992
2006
  titleTypographyProps,
@@ -2017,6 +2031,15 @@ const useStyles$x = makeStyles((theme) => ({
2017
2031
  fontWeight: "bold",
2018
2032
  color: theme.palette.textContrast
2019
2033
  },
2034
+ description: {
2035
+ fontSize: "100%",
2036
+ top: "50%",
2037
+ left: "50%",
2038
+ transform: "translate(-50%, -50%)",
2039
+ position: "absolute",
2040
+ wordBreak: "break-all",
2041
+ display: "inline-block"
2042
+ },
2020
2043
  circle: {
2021
2044
  width: "80%",
2022
2045
  transform: "translate(10%, 0)"
@@ -2048,16 +2071,35 @@ const getProgressColor = ({
2048
2071
  return palette.status.ok;
2049
2072
  };
2050
2073
  function Gauge(props) {
2074
+ const [hoverRef, setHoverRef] = useState(null);
2051
2075
  const { getColor = getProgressColor } = props;
2052
2076
  const classes = useStyles$x(props);
2053
2077
  const { palette } = useTheme();
2054
- const { value, fractional, inverse, unit, max } = {
2078
+ const { value, fractional, inverse, unit, max, description } = {
2055
2079
  ...defaultGaugeProps,
2056
2080
  ...props
2057
2081
  };
2058
2082
  const asPercentage = fractional ? Math.round(value * max) : value;
2059
2083
  const asActual = max !== 100 ? Math.round(value) : asPercentage;
2084
+ const [isHovering, setIsHovering] = useState(false);
2085
+ useEffect(() => {
2086
+ const node = hoverRef;
2087
+ const handleMouseOver = () => setIsHovering(true);
2088
+ const handleMouseOut = () => setIsHovering(false);
2089
+ if (node && description) {
2090
+ node.addEventListener("mouseenter", handleMouseOver);
2091
+ node.addEventListener("mouseleave", handleMouseOut);
2092
+ return () => {
2093
+ node.removeEventListener("mouseenter", handleMouseOver);
2094
+ node.removeEventListener("mouseleave", handleMouseOut);
2095
+ };
2096
+ }
2097
+ return () => {
2098
+ setIsHovering(false);
2099
+ };
2100
+ }, [description, hoverRef]);
2060
2101
  return /* @__PURE__ */ React.createElement("div", {
2102
+ ref: setHoverRef,
2061
2103
  className: classes.root
2062
2104
  }, /* @__PURE__ */ React.createElement(Circle, {
2063
2105
  strokeLinecap: "butt",
@@ -2066,7 +2108,9 @@ function Gauge(props) {
2066
2108
  trailWidth: 12,
2067
2109
  strokeColor: getColor({ palette, value: asActual, inverse, max }),
2068
2110
  className: classes.circle
2069
- }), /* @__PURE__ */ React.createElement("div", {
2111
+ }), description && isHovering ? /* @__PURE__ */ React.createElement("div", {
2112
+ className: classes.description
2113
+ }, description) : /* @__PURE__ */ React.createElement("div", {
2070
2114
  className: classes.overlay
2071
2115
  }, isNaN(value) ? "N/A" : `${asActual}${unit}`));
2072
2116
  }
@@ -2079,9 +2123,20 @@ const useStyles$w = makeStyles({
2079
2123
  }, { name: "BackstageGaugeCard" });
2080
2124
  function GaugeCard(props) {
2081
2125
  const classes = useStyles$w(props);
2082
- const { title, subheader, progress, inverse, deepLink, variant, getColor } = props;
2126
+ const {
2127
+ title,
2128
+ subheader,
2129
+ progress,
2130
+ inverse,
2131
+ deepLink,
2132
+ description,
2133
+ icon,
2134
+ variant,
2135
+ getColor
2136
+ } = props;
2083
2137
  const gaugeProps = {
2084
2138
  inverse,
2139
+ description,
2085
2140
  getColor,
2086
2141
  value: progress
2087
2142
  };
@@ -2091,7 +2146,8 @@ function GaugeCard(props) {
2091
2146
  title,
2092
2147
  subheader,
2093
2148
  deepLink,
2094
- variant
2149
+ variant,
2150
+ icon
2095
2151
  }, /* @__PURE__ */ React.createElement(Gauge, {
2096
2152
  ...gaugeProps
2097
2153
  })));
@@ -2763,8 +2819,9 @@ const DEFAULT_SUPPORT_CONFIG = {
2763
2819
  ]
2764
2820
  };
2765
2821
  function useSupportConfig() {
2766
- const config = useApi(configApiRef);
2767
- const supportConfig = config.getOptionalConfig("app.support");
2822
+ const apiHolder = useApiHolder();
2823
+ const config = apiHolder.get(configApiRef);
2824
+ const supportConfig = config == null ? void 0 : config.getOptionalConfig("app.support");
2768
2825
  if (!supportConfig) {
2769
2826
  return DEFAULT_SUPPORT_CONFIG;
2770
2827
  }
@@ -3115,6 +3172,9 @@ const useStyles$m = makeStyles((theme) => ({
3115
3172
  },
3116
3173
  overlayHeaderClose: {
3117
3174
  color: theme.palette.bursts.fontColor
3175
+ },
3176
+ marginMobileSidebar: {
3177
+ marginBottom: `${sidebarConfig.mobileSidebarHeight}px`
3118
3178
  }
3119
3179
  }));
3120
3180
  const sortSidebarGroupsForPriority = (children) => orderBy(children, ({ props: { priority } }) => Number.isInteger(priority) ? priority : -1, "desc");
@@ -3130,7 +3190,13 @@ const OverlayMenu = ({
3130
3190
  anchor: "bottom",
3131
3191
  open,
3132
3192
  onClose,
3133
- classes: { paperAnchorBottom: classes.overlay }
3193
+ ModalProps: {
3194
+ BackdropProps: { classes: { root: classes.marginMobileSidebar } }
3195
+ },
3196
+ classes: {
3197
+ root: classes.marginMobileSidebar,
3198
+ paperAnchorBottom: classes.overlay
3199
+ }
3134
3200
  }, /* @__PURE__ */ React.createElement(Box, {
3135
3201
  className: classes.overlayHeader
3136
3202
  }, /* @__PURE__ */ React.createElement(Typography, {
@@ -5010,7 +5076,7 @@ const useStyles$6 = makeStyles((theme) => ({
5010
5076
  }
5011
5077
  }), { name: "BackstageErrorPage" });
5012
5078
  function ErrorPage(props) {
5013
- const { status, statusMessage, additionalInfo } = props;
5079
+ const { status, statusMessage, additionalInfo, supportUrl } = props;
5014
5080
  const classes = useStyles$6();
5015
5081
  const navigate = useNavigate();
5016
5082
  const support = useSupportConfig();
@@ -5039,8 +5105,8 @@ function ErrorPage(props) {
5039
5105
  to: "#",
5040
5106
  "data-testid": "go-back-link",
5041
5107
  onClick: () => navigate(-1)
5042
- }, "Go back"), "... or please ", /* @__PURE__ */ React.createElement(ActualLink, {
5043
- to: support.url
5108
+ }, "Go back"), "... or please", " ", /* @__PURE__ */ React.createElement(ActualLink, {
5109
+ to: supportUrl || support.url
5044
5110
  }, "contact support"), " if you think this is a bug.")));
5045
5111
  }
5046
5112
 
@@ -5133,7 +5199,7 @@ const useStyles$5 = makeStyles((theme) => ({
5133
5199
  marginBottom: 0
5134
5200
  },
5135
5201
  subtitle: {
5136
- color: theme.palette.common.white,
5202
+ color: theme.palette.bursts.fontColor,
5137
5203
  opacity: 0.8,
5138
5204
  display: "inline-block",
5139
5205
  marginTop: theme.spacing(1),
@@ -5408,18 +5474,19 @@ function ItemCardGrid(props) {
5408
5474
  }
5409
5475
 
5410
5476
  const useStyles$1 = makeStyles(() => ({
5411
- root: {
5477
+ root: ({ isMobile }) => ({
5412
5478
  display: "grid",
5413
5479
  gridTemplateAreas: "'pageHeader pageHeader pageHeader' 'pageSubheader pageSubheader pageSubheader' 'pageNav pageContent pageSidebar'",
5414
5480
  gridTemplateRows: "max-content auto 1fr",
5415
5481
  gridTemplateColumns: "auto 1fr auto",
5416
- height: "100%",
5482
+ height: isMobile ? "100%" : "100vh",
5417
5483
  overflowY: "auto"
5418
- }
5484
+ })
5419
5485
  }), { name: "BackstagePage" });
5420
5486
  function Page(props) {
5421
5487
  const { themeId, children } = props;
5422
- const classes = useStyles$1();
5488
+ const { isMobile } = useContext(SidebarPinStateContext);
5489
+ const classes = useStyles$1({ isMobile });
5423
5490
  return /* @__PURE__ */ React.createElement(ThemeProvider, {
5424
5491
  theme: (baseTheme) => ({
5425
5492
  ...baseTheme,