core-components: split up LogViewer
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 React from 'react';
|
||||
import { AnsiLine, ChunkModifiers } from './AnsiProcessor';
|
||||
import startCase from 'lodash/startCase';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from './useStyles';
|
||||
|
||||
function getModifierClasses(
|
||||
classes: ReturnType<typeof useStyles>,
|
||||
modifiers: ChunkModifiers,
|
||||
) {
|
||||
const classNames = new Array<string>();
|
||||
if (modifiers.bold) {
|
||||
classNames.push(classes.modifierBold);
|
||||
}
|
||||
if (modifiers.italic) {
|
||||
classNames.push(classes.modifierItalic);
|
||||
}
|
||||
if (modifiers.underline) {
|
||||
classNames.push(classes.modifierUnderline);
|
||||
}
|
||||
if (modifiers.foreground) {
|
||||
const key = `modifierForeground${startCase(
|
||||
modifiers.foreground,
|
||||
)}` as keyof typeof classes;
|
||||
classNames.push(classes[key]);
|
||||
}
|
||||
if (modifiers.background) {
|
||||
const key = `modifierBackground${startCase(
|
||||
modifiers.background,
|
||||
)}` as keyof typeof classes;
|
||||
classNames.push(classes[key]);
|
||||
}
|
||||
return classNames.length > 0 ? classNames.join(' ') : undefined;
|
||||
}
|
||||
|
||||
export interface LogLineProps {
|
||||
line: AnsiLine;
|
||||
classes: ReturnType<typeof useStyles>;
|
||||
searchText: string;
|
||||
}
|
||||
|
||||
export function LogLine({ line, classes, searchText }: LogLineProps) {
|
||||
let searchResults: Array<{ start: number; end: number }> | undefined =
|
||||
undefined;
|
||||
if (searchText && line.text.includes(searchText)) {
|
||||
searchResults = [];
|
||||
let offset = 0;
|
||||
for (;;) {
|
||||
const start = line.text.indexOf(searchText, offset);
|
||||
if (start === -1) {
|
||||
break;
|
||||
}
|
||||
const end = start + searchText.length;
|
||||
searchResults.push({ start, end });
|
||||
offset = end;
|
||||
}
|
||||
}
|
||||
|
||||
const output = new Array<JSX.Element>(line.chunks.length);
|
||||
|
||||
let key = 0;
|
||||
let chunkOffset = 0;
|
||||
let nextResult = searchResults?.shift();
|
||||
for (const { text, modifiers } of line.chunks) {
|
||||
if (!nextResult || chunkOffset + text.length < nextResult.start) {
|
||||
output.push(
|
||||
<span key={key++} className={getModifierClasses(classes, modifiers)}>
|
||||
{text}
|
||||
</span>,
|
||||
);
|
||||
chunkOffset += text.length;
|
||||
continue;
|
||||
}
|
||||
|
||||
let localOffset = 0;
|
||||
while (nextResult) {
|
||||
let localStart = nextResult.start - chunkOffset;
|
||||
if (localStart < 0) {
|
||||
localStart = 0;
|
||||
}
|
||||
const localEnd = nextResult.end - chunkOffset;
|
||||
const beforeMatch = text.slice(localOffset, localStart);
|
||||
const match = text.slice(localStart, localEnd);
|
||||
|
||||
if (beforeMatch) {
|
||||
output.push(
|
||||
<span key={key++} className={getModifierClasses(classes, modifiers)}>
|
||||
{beforeMatch}
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
output.push(
|
||||
<span
|
||||
key={key++}
|
||||
className={clsx(
|
||||
getModifierClasses(classes, modifiers),
|
||||
classes.textHighlight,
|
||||
)}
|
||||
>
|
||||
{match}
|
||||
</span>,
|
||||
);
|
||||
|
||||
localOffset = localStart + match.length;
|
||||
|
||||
if (match.length === searchText.length) {
|
||||
nextResult = searchResults?.shift();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (localOffset < text.length) {
|
||||
output.push(
|
||||
<span key={key++} className={getModifierClasses(classes, modifiers)}>
|
||||
{text.slice(localOffset)}
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
|
||||
chunkOffset += text.length;
|
||||
}
|
||||
return <>{output}</>;
|
||||
}
|
||||
@@ -14,17 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { alpha, makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { AnsiChunk, AnsiLine, AnsiProcessor } from './AnsiProcessor';
|
||||
import startCase from 'lodash/startCase';
|
||||
import * as colors from '@material-ui/core/colors';
|
||||
import { AnsiProcessor } from './AnsiProcessor';
|
||||
import { HEADER_SIZE, useStyles } from './styles';
|
||||
import clsx from 'clsx';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
|
||||
const HEADER_SIZE = 40;
|
||||
import { LogLine } from './LogLine';
|
||||
|
||||
export interface LogViewerProps {
|
||||
text: string;
|
||||
@@ -50,230 +47,6 @@ export interface ChunkModifiers {
|
||||
underline?: boolean;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
background: theme.palette.background.paper,
|
||||
},
|
||||
header: {
|
||||
height: HEADER_SIZE,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
log: {
|
||||
fontFamily: '"Monaco", monospace',
|
||||
fontSize: theme.typography.fontSize,
|
||||
},
|
||||
line: {
|
||||
whiteSpace: 'pre',
|
||||
|
||||
'&:hover': {
|
||||
background: theme.palette.action.hover,
|
||||
},
|
||||
},
|
||||
lineSelected: {
|
||||
background: theme.palette.action.selected,
|
||||
|
||||
'&:hover': {
|
||||
background: theme.palette.action.selected,
|
||||
},
|
||||
},
|
||||
lineNumber: {
|
||||
display: 'inline-block',
|
||||
textAlign: 'end',
|
||||
width: 60,
|
||||
marginRight: theme.spacing(1),
|
||||
cursor: 'pointer',
|
||||
},
|
||||
textHighlight: {
|
||||
background: alpha(theme.palette.primary.main, 0.3),
|
||||
},
|
||||
modifierBold: {
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
},
|
||||
modifierItalic: {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
modifierUnderline: {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
modifierForegroundBlack: {
|
||||
color: colors.common.black,
|
||||
},
|
||||
modifierForegroundRed: {
|
||||
color: colors.red[500],
|
||||
},
|
||||
modifierForegroundGreen: {
|
||||
color: colors.green[500],
|
||||
},
|
||||
modifierForegroundYellow: {
|
||||
color: colors.yellow[500],
|
||||
},
|
||||
modifierForegroundBlue: {
|
||||
color: colors.blue[500],
|
||||
},
|
||||
modifierForegroundMagenta: {
|
||||
color: colors.purple[500],
|
||||
},
|
||||
modifierForegroundCyan: {
|
||||
color: colors.cyan[500],
|
||||
},
|
||||
modifierForegroundWhite: {
|
||||
color: colors.common.white,
|
||||
},
|
||||
modifierForegroundGrey: {
|
||||
color: colors.grey[500],
|
||||
},
|
||||
modifierBackgroundBlack: {
|
||||
background: colors.common.black,
|
||||
},
|
||||
modifierBackgroundRed: {
|
||||
background: colors.red[500],
|
||||
},
|
||||
modifierBackgroundGreen: {
|
||||
background: colors.green[500],
|
||||
},
|
||||
modifierBackgroundYellow: {
|
||||
background: colors.yellow[500],
|
||||
},
|
||||
modifierBackgroundBlue: {
|
||||
background: colors.blue[500],
|
||||
},
|
||||
modifierBackgroundMagenta: {
|
||||
background: colors.purple[500],
|
||||
},
|
||||
modifierBackgroundCyan: {
|
||||
background: colors.cyan[500],
|
||||
},
|
||||
modifierBackgroundWhite: {
|
||||
background: colors.common.white,
|
||||
},
|
||||
modifierBackgroundGrey: {
|
||||
background: colors.grey[500],
|
||||
},
|
||||
}));
|
||||
|
||||
function getModifierClasses(
|
||||
classes: ReturnType<typeof useStyles>,
|
||||
modifiers: ChunkModifiers,
|
||||
) {
|
||||
const classNames = new Array<string>();
|
||||
if (modifiers.bold) {
|
||||
classNames.push(classes.modifierBold);
|
||||
}
|
||||
if (modifiers.italic) {
|
||||
classNames.push(classes.modifierItalic);
|
||||
}
|
||||
if (modifiers.underline) {
|
||||
classNames.push(classes.modifierUnderline);
|
||||
}
|
||||
if (modifiers.foreground) {
|
||||
const key = `modifierForeground${startCase(
|
||||
modifiers.foreground,
|
||||
)}` as keyof typeof classes;
|
||||
classNames.push(classes[key]);
|
||||
}
|
||||
if (modifiers.background) {
|
||||
const key = `modifierBackground${startCase(
|
||||
modifiers.background,
|
||||
)}` as keyof typeof classes;
|
||||
classNames.push(classes[key]);
|
||||
}
|
||||
return classNames.length > 0 ? classNames.join(' ') : undefined;
|
||||
}
|
||||
|
||||
export function LogLine({
|
||||
line,
|
||||
classes,
|
||||
searchText,
|
||||
}: {
|
||||
line: AnsiLine;
|
||||
classes: ReturnType<typeof useStyles>;
|
||||
searchText: string;
|
||||
}) {
|
||||
let searchResults: Array<{ start: number; end: number }> | undefined =
|
||||
undefined;
|
||||
if (searchText && line.text.includes(searchText)) {
|
||||
searchResults = [];
|
||||
let offset = 0;
|
||||
for (;;) {
|
||||
const start = line.text.indexOf(searchText, offset);
|
||||
if (start === -1) {
|
||||
break;
|
||||
}
|
||||
const end = start + searchText.length;
|
||||
searchResults.push({ start, end });
|
||||
offset = end;
|
||||
}
|
||||
}
|
||||
|
||||
const output = new Array<JSX.Element>(line.chunks.length);
|
||||
|
||||
let key = 0;
|
||||
let chunkOffset = 0;
|
||||
let nextResult = searchResults?.shift();
|
||||
for (const { text, modifiers } of line.chunks) {
|
||||
if (!nextResult || chunkOffset + text.length < nextResult.start) {
|
||||
output.push(
|
||||
<span key={key++} className={getModifierClasses(classes, modifiers)}>
|
||||
{text}
|
||||
</span>,
|
||||
);
|
||||
chunkOffset += text.length;
|
||||
continue;
|
||||
}
|
||||
|
||||
let localOffset = 0;
|
||||
while (nextResult) {
|
||||
let localStart = nextResult.start - chunkOffset;
|
||||
if (localStart < 0) {
|
||||
localStart = 0;
|
||||
}
|
||||
const localEnd = nextResult.end - chunkOffset;
|
||||
const beforeMatch = text.slice(localOffset, localStart);
|
||||
const match = text.slice(localStart, localEnd);
|
||||
|
||||
if (beforeMatch) {
|
||||
output.push(
|
||||
<span key={key++} className={getModifierClasses(classes, modifiers)}>
|
||||
{beforeMatch}
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
output.push(
|
||||
<span
|
||||
key={key++}
|
||||
className={clsx(
|
||||
getModifierClasses(classes, modifiers),
|
||||
classes.textHighlight,
|
||||
)}
|
||||
>
|
||||
{match}
|
||||
</span>,
|
||||
);
|
||||
|
||||
localOffset = localStart + match.length;
|
||||
|
||||
if (match.length === searchText.length) {
|
||||
nextResult = searchResults?.shift();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (localOffset < text.length) {
|
||||
output.push(
|
||||
<span key={key++} className={getModifierClasses(classes, modifiers)}>
|
||||
{text.slice(localOffset)}
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
|
||||
chunkOffset += text.length;
|
||||
}
|
||||
return <>{output}</>;
|
||||
}
|
||||
|
||||
export function LogViewer(props: LogViewerProps) {
|
||||
const { noLineNumbers } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { alpha, makeStyles } from '@material-ui/core/styles';
|
||||
import * as colors from '@material-ui/core/colors';
|
||||
|
||||
export const HEADER_SIZE = 40;
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
background: theme.palette.background.paper,
|
||||
},
|
||||
header: {
|
||||
height: HEADER_SIZE,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
log: {
|
||||
fontFamily: '"Monaco", monospace',
|
||||
fontSize: theme.typography.fontSize,
|
||||
},
|
||||
line: {
|
||||
whiteSpace: 'pre',
|
||||
|
||||
'&:hover': {
|
||||
background: theme.palette.action.hover,
|
||||
},
|
||||
},
|
||||
lineSelected: {
|
||||
background: theme.palette.action.selected,
|
||||
|
||||
'&:hover': {
|
||||
background: theme.palette.action.selected,
|
||||
},
|
||||
},
|
||||
lineNumber: {
|
||||
display: 'inline-block',
|
||||
textAlign: 'end',
|
||||
width: 60,
|
||||
marginRight: theme.spacing(1),
|
||||
cursor: 'pointer',
|
||||
},
|
||||
textHighlight: {
|
||||
background: alpha(theme.palette.primary.main, 0.3),
|
||||
},
|
||||
modifierBold: {
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
},
|
||||
modifierItalic: {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
modifierUnderline: {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
modifierForegroundBlack: {
|
||||
color: colors.common.black,
|
||||
},
|
||||
modifierForegroundRed: {
|
||||
color: colors.red[500],
|
||||
},
|
||||
modifierForegroundGreen: {
|
||||
color: colors.green[500],
|
||||
},
|
||||
modifierForegroundYellow: {
|
||||
color: colors.yellow[500],
|
||||
},
|
||||
modifierForegroundBlue: {
|
||||
color: colors.blue[500],
|
||||
},
|
||||
modifierForegroundMagenta: {
|
||||
color: colors.purple[500],
|
||||
},
|
||||
modifierForegroundCyan: {
|
||||
color: colors.cyan[500],
|
||||
},
|
||||
modifierForegroundWhite: {
|
||||
color: colors.common.white,
|
||||
},
|
||||
modifierForegroundGrey: {
|
||||
color: colors.grey[500],
|
||||
},
|
||||
modifierBackgroundBlack: {
|
||||
background: colors.common.black,
|
||||
},
|
||||
modifierBackgroundRed: {
|
||||
background: colors.red[500],
|
||||
},
|
||||
modifierBackgroundGreen: {
|
||||
background: colors.green[500],
|
||||
},
|
||||
modifierBackgroundYellow: {
|
||||
background: colors.yellow[500],
|
||||
},
|
||||
modifierBackgroundBlue: {
|
||||
background: colors.blue[500],
|
||||
},
|
||||
modifierBackgroundMagenta: {
|
||||
background: colors.purple[500],
|
||||
},
|
||||
modifierBackgroundCyan: {
|
||||
background: colors.cyan[500],
|
||||
},
|
||||
modifierBackgroundWhite: {
|
||||
background: colors.common.white,
|
||||
},
|
||||
modifierBackgroundGrey: {
|
||||
background: colors.grey[500],
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user