diff --git a/.changeset/tame-birds-thank.md b/.changeset/tame-birds-thank.md new file mode 100644 index 0000000000..b3dae294f6 --- /dev/null +++ b/.changeset/tame-birds-thank.md @@ -0,0 +1,5 @@ +--- +'@hyperdx/app': minor +--- + +feat: Log Side Panel styling diff --git a/packages/app/pages/_app.tsx b/packages/app/pages/_app.tsx index 30de433653..9f5c7e8e1a 100644 --- a/packages/app/pages/_app.tsx +++ b/packages/app/pages/_app.tsx @@ -78,6 +78,7 @@ export default function MyApp({ Component, pageProps }: AppProps) { name="viewport" content="width=device-width, initial-scale=0.75" /> + diff --git a/packages/app/src/HDXTableChart.tsx b/packages/app/src/HDXTableChart.tsx index b919b857df..4b5e41d8a2 100644 --- a/packages/app/src/HDXTableChart.tsx +++ b/packages/app/src/HDXTableChart.tsx @@ -1,13 +1,13 @@ import { - ColumnDef, flexRender, getCoreRowModel, Row as TableRow, useReactTable, } from '@tanstack/react-table'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { useState, useCallback, useRef, useEffect, useMemo, memo } from 'react'; +import { useCallback, useRef, memo } from 'react'; import cx from 'classnames'; +import { UNDEFINED_WIDTH } from './tableUtils'; import api from './api'; import { AggFn } from './ChartUtils'; @@ -19,9 +19,6 @@ const Table = ({ data: any[]; valueColumnName: string; }) => { - // https://github.com/TanStack/table/discussions/3192#discussioncomment-3873093 - const UNDEFINED_WIDTH = 99999; - //we need a reference to the scrolling element for logic down below const tableContainerRef = useRef(null); diff --git a/packages/app/src/LogSidePanel.tsx b/packages/app/src/LogSidePanel.tsx index 37620269bf..75a7947c2b 100644 --- a/packages/app/src/LogSidePanel.tsx +++ b/packages/app/src/LogSidePanel.tsx @@ -8,7 +8,6 @@ import cx from 'classnames'; import get from 'lodash/get'; import isPlainObject from 'lodash/isPlainObject'; import mapValues from 'lodash/mapValues'; -import omit from 'lodash/omit'; import pickBy from 'lodash/pickBy'; import stripAnsi from 'strip-ansi'; import { ErrorBoundary } from 'react-error-boundary'; @@ -20,6 +19,19 @@ import { toast } from 'react-toastify'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useQueryParam } from 'use-query-params'; +import type { StacktraceFrame, StacktraceBreadcrumb } from './types'; +import { + networkColumns, + stacktraceColumns, + breadcrumbColumns, + headerColumns, + StacktraceValue, + CollapsibleSection, + SectionWrapper, + NetworkBody, + LogSidePanelKbdShortcuts, +} from './LogSidePanelElements'; +import { Table } from './components/Table'; import api from './api'; import LogLevel from './LogLevel'; @@ -27,7 +39,6 @@ import SearchInput from './SearchInput'; import TabBar from './TabBar'; import TimelineChart from './TimelineChart'; import SessionSubpanel from './SessionSubpanel'; -import LogSidePanelKbdShortcuts from './LogSidePanelKbdShortcuts'; import { formatDistanceToNowStrictShort, useFirstNonNullValue, @@ -48,7 +59,7 @@ const HDX_BODY_FIELD = '_hdx_body'; // https://github.com/reduxjs/redux-devtools/blob/f11383d294c1139081f119ef08aa1169bd2ad5ff/packages/react-json-tree/src/createStylingFromTheme.ts const JSON_TREE_THEME = { - base00: '#0F1216', + base00: '#00000000', base01: '#383830', base02: '#49483e', base03: '#75715e', @@ -548,6 +559,31 @@ function TraceSubpanel({ // Allows us to determine if the user has changed the search query const searchedQuery = _searchedQuery ?? ''; const isException = isExceptionSpan({ logData: selectedLogData }); + + const exceptionBreadcrumbs = useMemo(() => { + try { + return JSON.parse( + selectedLogData?.['string.values']?.[ + selectedLogData?.['string.names']?.indexOf('breadcrumbs') + ] ?? '[]', + ); + } catch (e) { + return []; + } + }, [selectedLogData]); + + const exceptionValues = useMemo(() => { + try { + return JSON.parse( + selectedLogData?.['string.values']?.[ + selectedLogData?.['string.names']?.indexOf('exception.values') + ] ?? '[]', + ); + } catch (e) { + return []; + } + }, [selectedLogData]); + // Clear search query when we close the panel // TODO: This doesn't work because it breaks navigation to things like the sessions page, // probably due to a race condition. Need to fix later. @@ -596,13 +632,14 @@ function TraceSubpanel({ setSelectedLog({ id, sortKey }); }} /> -
+ +
{selectedLogData != null ? ( <>
- - {selectedLogData.type === 'span' ? 'Span' : 'Log'} Details for:{' '} - +
+ {selectedLogData.type === 'span' ? 'Span' : 'Log'} Details +
[] {' '} @@ -655,44 +692,32 @@ function TraceSubpanel({ )} > )} {!isException && ( - <> - { - console.error(err); - }} - fallbackRender={() => ( -
- An error occurred while rendering event properties. -
- )} - > - -
- + { + console.error(err); + }} + fallbackRender={() => ( +
+ An error occurred while rendering event properties. +
+ )} + > + +
)} { @@ -822,7 +847,7 @@ function EventTagSubpanel({ return (
-
Event Tags
+
Event Tags
{Object.entries(properties).map(([key, value]) => { let commandArgs = ''; @@ -891,50 +916,6 @@ function ExceptionEvent({ ); } -function CollapsibleSection({ - title, - children, - initiallyCollapsed, -}: { - title: string; - children: React.ReactNode; - initiallyCollapsed?: boolean; -}) { - const [collapsed, setCollapsed] = useState(initiallyCollapsed ?? false); - return ( -
-
setCollapsed(!collapsed)} - > - -
{title}
-
- {collapsed ? null : children} -
- ); -} - -function NetworkPropertyRow({ - label, - value, - width, - className, -}: { - label: React.ReactNode; - value: React.ReactNode; - width: number; - className?: string; -}) { - return ( -
-
{label}
-
{value}
-
- ); -} - const parseHeaders = ( keyPrefix: string, parsedProperties: any, @@ -1080,7 +1061,6 @@ function NetworkPropertySubpanel({ field: string; groupBy: string[]; }) => string; - onPropertyAddClick?: (key: string, value: string) => void; }) { const parsedProperties = useParsedLogProperties(logData); @@ -1093,8 +1073,6 @@ function NetworkPropertySubpanel({ return parseHeaders('http.response.header.', parsedProperties); }, [parsedProperties]); - const LABEL_WIDTH = 200; - const url = parsedProperties['http.url']; const remoteAddress = parsedProperties['net.peer.ip']; const statusCode = parsedProperties['http.status_code']; @@ -1145,100 +1123,61 @@ function NetworkPropertySubpanel({
- - - {remoteAddress != null && ( - - )} - {statusCode != null && ( - + = 500 ? 'text-danger' : statusCode >= 400 ? 'text-warning' - : 'text-success' - }`} - > - {statusCode} {parsedProperties['http.status_text'] ?? ''} - - } - width={LABEL_WIDTH} + : 'text-success', + }, + ].filter(Boolean)} + hideHeader /> - )} + + {requestHeaders.length > 0 && ( - {requestHeaders.length > 0 ? ( - requestHeaders.map(([key, value]) => ( - - )) - ) : ( -
No request headers collected
- )} + +
+ )} + {requestBody != null && ( - - {requestBody != null ? ( -
-
-                {typeof requestBody === 'string' ? (
-                  requestBody
-                ) : (
-                   true}
-                    data={requestBody}
-                    theme={JSON_TREE_THEME}
-                    valueRenderer={(raw, value, ...keyPath) => {
-                      return (
-                        
-                          {raw}
-                        
- ); - }} - /> - )} -
-
- ) : ( -
No request body collected
- )} + + + + )} {responseHeaders.length > 0 && ( @@ -1246,57 +1185,28 @@ function NetworkPropertySubpanel({ title={`Response Headers (${responseHeaders.length})`} initiallyCollapsed > - {responseHeaders.length > 0 ? ( - responseHeaders.map(([key, value]) => ( - - )) - ) : ( -
No response headers collected
- )} + +
+ )} {responseBody != null && ( - - {responseBody != null && responseBody != '' ? ( -
-
-                {typeof responseBody === 'string' ? (
-                  responseBody
-                ) : (
-                   true}
-                    data={responseBody}
-                    theme={JSON_TREE_THEME}
-                    valueRenderer={raw => {
-                      return (
-                        
-                          {raw}
-                        
- ); - }} - /> - )} -
-
- ) : responseBody === '' ? ( -
Empty response
- ) : ( -
No response body collected
- )} + + + + )} @@ -1939,131 +1849,97 @@ const ExceptionSubpanel = ({ breadcrumbs, exceptionValues, }: { - breadcrumbs: { - type?: string; - level?: string; - event_id?: string; - category?: string; - message?: string; - data?: { [key: string]: any }; - timestamp: number; - }[]; + breadcrumbs?: StacktraceBreadcrumb[]; exceptionValues: { type: string; value: string; mechanism?: { type: string; handled: boolean; + data?: { + // TODO: Are these fields dynamic? + function?: string; + handler?: string; + target?: string; + }; }; stacktrace?: { - frames: { - filename: string; - function: string; - module?: string; - lineno: number; - colno: number; - in_app: boolean; - context_line?: string; - pre_context?: string[]; - post_context?: string[]; - }[]; + frames: StacktraceFrame[]; }; }[]; }) => { const firstException = exceptionValues[0]; + const stacktraceFrames = useMemo( + () => firstException.stacktrace?.frames.reverse() ?? [], + [firstException.stacktrace?.frames], + ); + // TODO: show all frames (stackable) return (
- - {firstException ? ( -
-
{firstException.type}
-
{firstException.value}
-
- mechanism: {firstException.mechanism?.type} - - handled:{' '} - {firstException.mechanism?.handled ? ( - true - ) : ( - false - )} - -
- {firstException.stacktrace?.frames?.reverse().map((frame, i) => ( -
-
- {frame.filename} in {frame.function} at line {frame.lineno}: - {frame.colno} -
-
-                  {frame.pre_context?.map((line, i) => (
-                    
- {(frame.lineno ?? 0) - - (frame.pre_context?.length ?? 0) + - i}{' '} - {line} -
- ))} - {frame.context_line && ( -
- {frame.lineno} {frame.context_line} -
- )} - {frame.post_context?.map((line, i) => ( -
- {frame.lineno + i + 1} {line} -
- ))} -
+ + +
+
{firstException.type}
+
{firstException.value}
- ))} -
- ) : ( -
No Stack Trace Found
- )} - - - {breadcrumbs.length > 0 ? ( - breadcrumbs.map((event, i) => ( -
-
- {format(new Date(event.timestamp * 1000), 'MMM d HH:mm:ss.SSS')} +
+ + true + ) : ( + false + ) + } + /> + {firstException.mechanism?.data?.function ? ( + + ) : null} + {firstException.mechanism?.data?.handler ? ( + + ) : null} + {firstException.mechanism?.data?.target ? ( + + ) : null}
- true} - data={omit(event, ['timestamp'])} - theme={JSON_TREE_THEME} - valueRenderer={(raw, value, ...keyPath) => { - return ( -
-                      {raw}
-                    
- ); - }} - /> -
- )) - ) : ( -
No Breadcrumbs Found
- )} + + } + > +
+ + + + + +
+ ); @@ -2213,7 +2089,7 @@ export default function LogSidePanel({ onClose={_onClose} /> { + const [collapsed, setCollapsed] = React.useState(initiallyCollapsed ?? false); + + return ( +
+
setCollapsed(!collapsed)} + > + +
{title}
+
+ {collapsed ? null :
{children}
} +
+ ); +}; + +export const SectionWrapper: React.FC<{ title?: React.ReactNode }> = ({ + children, + title, +}) => ( +
+ {title &&
{title}
} + {children} +
+); + +/** + * Stacktrace elements + */ +export const StacktraceValue = ({ + label, + value, +}: { + label: React.ReactNode; + value: React.ReactNode; +}) => { + return ( +
+
{label}
+
{value}
+
+ ); +}; + +const StacktraceRowExpandButton = ({ + onClick, + isOpen, +}: { + onClick: VoidFunction; + isOpen: boolean; +}) => { + return ( + + ); +}; + +export const StacktraceRow = ({ row }: { row: Row }) => { + const [lineContextOpen, setLineContextOpen] = React.useState(true); + + const frame = row.original; + const hasContext = !!frame.context_line; + + const handleToggleContext = React.useCallback(() => { + setLineContextOpen(!lineContextOpen); + }, [lineContextOpen]); + + return ( + <> +
+
+ {frame.filename} + {' in '} + {frame.function} + {frame.lineno || frame.colno ? ( + <> + {' at line '} + + {frame.lineno}:{frame.colno} + + + ) : null} +
+ {hasContext && ( + + )} +
+ + {lineContextOpen && hasContext && ( +
+          {frame.pre_context?.map((line, i) => (
+            
+ + {(frame.lineno ?? 0) - (frame.pre_context?.length ?? 0) + i} + + {line} +
+ ))} + {frame.context_line && ( +
+ {frame.lineno} + {frame.context_line} +
+ )} + {frame.post_context?.map((line, i) => ( +
+ + {frame.lineno + i + 1} + + {line} +
+ ))} +
+ )} + + ); +}; + +export const stacktraceColumns: ColumnDef[] = [ + { + accessorKey: 'filename', + cell: StacktraceRow, + }, +]; + +export const breadcrumbColumns: ColumnDef[] = [ + { + accessorKey: 'category', + header: 'Category', + cell: ({ row }) => ( + {row.original.category} + ), + }, + { + accessorKey: 'message', + header: 'Message', + size: UNDEFINED_WIDTH, + cell: ({ row }) => + row.original.message || Empty, + }, + { + accessorKey: 'level', + header: '', + size: 140, + cell: ({ row }) => + row.original.level ? : null, + }, + { + header: 'Timestamp', + size: 220, + cell: ({ row }) => ( + + {format(new Date(row.original.timestamp * 1000), 'MMM d HH:mm:ss.SSS')} + + ), + }, +]; + +/** + * Request / Response Headers elements + */ +export const headerColumns: ColumnDef<[string, string]>[] = [ + { + accessorKey: '0', + header: 'Header', + size: 260, + cell: ({ row }) => ( + + {row.original[0]} + + ), + }, + { + size: UNDEFINED_WIDTH, + accessorKey: '1', + header: 'Value', + }, +]; + +/** + * Network Subpanel + */ +export const networkColumns: ColumnDef<{ + label: string; + value: string; + className?: string; +}>[] = [ + { + accessorKey: 'label', + header: 'Label', + size: 260, + cell: ({ row }) => ( + {row.original.label} + ), + }, + { + size: UNDEFINED_WIDTH, + accessorKey: 'value', + header: 'Value', + cell: ({ row }) => ( + {row.original.value} + ), + }, +]; + +export const NetworkBody = ({ + body, + theme, + emptyMessage, + notCollectedMessage, +}: { + body: any; + theme?: any; + emptyMessage?: string; + notCollectedMessage?: string; +}) => { + const valueRenderer = React.useCallback(raw => { + return ( +
+        {raw}
+      
+ ); + }, []); + + return ( + <> + {body != null && body != '' ? ( +
+          {typeof body === 'string' ? (
+            body
+          ) : (
+            
+          )}
+        
+ ) : body === '' ? ( +
{emptyMessage}
+ ) : ( +
{notCollectedMessage}
+ )} + + ); +}; + +/** + * Keyboard shortcuts + */ +const Kbd = ({ children }: { children: string }) => ( +
{children}
+); + +export const LogSidePanelKbdShortcuts = () => { + const [isDismissed, setDismissed] = useLocalStorage( + 'kbd-shortcuts-dismissed', + false, + ); + + const handleDismiss = React.useCallback(() => { + setDismissed(true); + }, [setDismissed]); + + if (isDismissed) { + return null; + } + + return ( +
+
+
+
+ Use + arrow keys to move through events +
+
+
+ ESC to close +
+
+ +
+
+ ); +}; diff --git a/packages/app/src/LogSidePanelKbdShortcuts.tsx b/packages/app/src/LogSidePanelKbdShortcuts.tsx deleted file mode 100644 index e3b2e1ae67..0000000000 --- a/packages/app/src/LogSidePanelKbdShortcuts.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import styles from '../styles/LogSidePanel.module.scss'; -import { CloseButton } from 'react-bootstrap'; -import { useLocalStorage } from './utils'; -import * as React from 'react'; - -const Kbd = ({ children }: { children: string }) => ( -
{children}
-); - -export default function LogSidePanelKbdShortcuts() { - const [isDismissed, setDismissed] = useLocalStorage( - 'kbd-shortcuts-dismissed', - false, - ); - - const handleDismiss = React.useCallback(() => { - setDismissed(true); - }, []); - - if (isDismissed) { - return null; - } - - return ( -
-
-
-
- Use - arrow keys to move through events -
-
-
- ESC to close -
-
- -
-
- ); -} diff --git a/packages/app/src/LogTable.tsx b/packages/app/src/LogTable.tsx index dfcb32c4e7..7b4e403f04 100644 --- a/packages/app/src/LogTable.tsx +++ b/packages/app/src/LogTable.tsx @@ -25,6 +25,7 @@ import api from './api'; import { useLocalStorage, usePrevious, useWindowSize } from './utils'; import { useSearchEventStream } from './search'; import { useHotkeys } from 'react-hotkeys-hook'; +import { UNDEFINED_WIDTH } from './tableUtils'; import styles from '../styles/LogTable.module.scss'; @@ -279,8 +280,6 @@ export const RawLogTable = memo( const tsFormat = 'MMM d HH:mm:ss.SSS'; const tsShortFormat = 'HH:mm:ss'; - // https://github.com/TanStack/table/discussions/3192#discussioncomment-3873093 - const UNDEFINED_WIDTH = 99999; //once the user has scrolled within 500px of the bottom of the table, fetch more data if there is any const FETCH_NEXT_PAGE_PX = 500; diff --git a/packages/app/src/PatternTable.tsx b/packages/app/src/PatternTable.tsx index df5b242403..1dc11b1905 100644 --- a/packages/app/src/PatternTable.tsx +++ b/packages/app/src/PatternTable.tsx @@ -25,6 +25,7 @@ import api from './api'; import { useWindowSize } from './utils'; import { Pattern } from './PatternSidePanel'; import { Granularity, timeBucketByGranularity } from './ChartUtils'; +import { UNDEFINED_WIDTH } from './tableUtils'; const PatternTrendChartTooltip = (props: any) => { return null; @@ -150,9 +151,6 @@ const MemoPatternTable = memo( const { width } = useWindowSize(); const isSmallScreen = (width ?? 1000) < 900; - // https://github.com/TanStack/table/discussions/3192#discussioncomment-3873093 - const UNDEFINED_WIDTH = 99999; - //we need a reference to the scrolling element for logic down below const tableContainerRef = useRef(null); diff --git a/packages/app/src/components/Table.module.scss b/packages/app/src/components/Table.module.scss new file mode 100644 index 0000000000..6a8552c3e5 --- /dev/null +++ b/packages/app/src/components/Table.module.scss @@ -0,0 +1,101 @@ +@import '../../styles/variables'; + +$compactVerticalPadding: 6px; +$normalVerticalPadding: 10px; +$comfortableVerticalPadding: 14px; + +$horizontalPadding: 12px; + +.tableWrapper { + table { + width: 100%; + + th, + td { + &:first-child { + padding-left: $horizontalPadding * 2; + } + &:last-child { + padding-right: $horizontalPadding * 2; + } + } + + tr { + border-bottom: 1px solid $slate-950; + } + + tbody { + tr:last-child { + border-bottom: none; + } + } + + th { + color: $slate-300; + font-family: Inter; + text-transform: uppercase; + font-size: 9px; + font-weight: 500; + letter-spacing: 1px; + padding: $normalVerticalPadding $horizontalPadding; + } + + td { + padding: $normalVerticalPadding $horizontalPadding; + word-wrap: break-word; + word-break: break-all; + vertical-align: top; + } + } + + // Borderless + &.tableBorderless { + padding: 4px 0; + tr { + border-bottom: none; + } + } + + // Density + &.tableDensityCompact { + th, + td { + padding: $compactVerticalPadding $horizontalPadding; + } + } + + &.tableDensityComfortable { + th, + td { + padding: $comfortableVerticalPadding $horizontalPadding; + } + } +} + +.emptyMessage { + color: $slate-500; + text-align: center; + padding: 10px; +} + +.tableCellButton { + display: flex; + gap: 4px; + border-radius: 4px; + background-color: $slate-950; + border: 1px solid $slate-900; + color: $slate-300; + text-align: center; + font-size: 11px; + font-family: Inter; + + &:hover { + background-color: $slate-900; + border-color: $slate-800; + } + + &:active { + background-color: $slate-950; + border-color: $slate-950; + } +} diff --git a/packages/app/src/components/Table.tsx b/packages/app/src/components/Table.tsx new file mode 100644 index 0000000000..d29c4ee396 --- /dev/null +++ b/packages/app/src/components/Table.tsx @@ -0,0 +1,108 @@ +import * as React from 'react'; +import cx from 'classnames'; +import { + useReactTable, + getCoreRowModel, + flexRender, + ColumnDef, +} from '@tanstack/react-table'; +import { UNDEFINED_WIDTH } from '../tableUtils'; + +import styles from './Table.module.scss'; + +type TableProps = { + data?: T[]; + columns: ColumnDef[]; + emptyMessage?: string; + hideHeader?: boolean; + borderless?: boolean; + density?: 'compact' | 'normal' | 'comfortable'; +}; + +export const Table = ({ + data = [], + columns, + emptyMessage, + hideHeader, + borderless, + density = 'normal', +}: TableProps) => { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + if (!data.length) { + return
{emptyMessage}
; + } + + return ( +
+
+ {!hideHeader && ( + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + )} + + {table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ))} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ ); +}; + +export const TableCellButton: React.VFC<{ + title?: string; + label: React.ReactNode; + biIcon?: string; + onClick: VoidFunction; +}> = ({ onClick, title, label, biIcon }) => { + return ( + + ); +}; diff --git a/packages/app/src/tableUtils.tsx b/packages/app/src/tableUtils.tsx new file mode 100644 index 0000000000..52d7bcda4e --- /dev/null +++ b/packages/app/src/tableUtils.tsx @@ -0,0 +1,2 @@ +// https://github.com/TanStack/table/discussions/3192#discussioncomment-3873093 +export const UNDEFINED_WIDTH = 99999; diff --git a/packages/app/src/types.ts b/packages/app/src/types.ts index 9356186dff..c484e23043 100644 --- a/packages/app/src/types.ts +++ b/packages/app/src/types.ts @@ -104,3 +104,25 @@ export type Session = { export type Dictionary = { [key: string]: T; }; + +export type StacktraceFrame = { + filename: string; + function: string; + module?: string; + lineno: number; + colno: number; + in_app: boolean; + context_line?: string; + pre_context?: string[]; + post_context?: string[]; +}; + +export type StacktraceBreadcrumb = { + type?: string; + level?: string; + event_id?: string; + category?: string; + message?: string; + data?: { [key: string]: any }; + timestamp: number; +}; diff --git a/packages/app/src/utils.tsx b/packages/app/src/utils.tsx index 2699e73231..09594dfafd 100644 --- a/packages/app/src/utils.tsx +++ b/packages/app/src/utils.tsx @@ -1,7 +1,6 @@ import { format as fnsFormat, formatDistanceToNowStrict } from 'date-fns'; import { useRouter } from 'next/router'; import { useState, useEffect, useRef } from 'react'; -import Convert from 'ansi-to-html'; import type { MutableRefObject } from 'react'; diff --git a/packages/app/styles/LogSidePanel.module.scss b/packages/app/styles/LogSidePanel.module.scss index b0c47c99a3..f233815254 100644 --- a/packages/app/styles/LogSidePanel.module.scss +++ b/packages/app/styles/LogSidePanel.module.scss @@ -23,6 +23,7 @@ $padding-x: 24px; .panelDetails { padding: $padding-x; + padding-bottom: 16px; display: flex; flex-direction: column; gap: 12px; @@ -48,6 +49,18 @@ $padding-x: 24px; flex-grow: 1; } +.panelSectionWrapper { + background-color: #14171b; + margin: 10px -24px 24px; + border-top: 1px solid $slate-950; + border-bottom: 1px solid $slate-950; +} + +.panelSectionWrapperTitle { + padding: 18px 24px; + border-bottom: 1px solid $slate-950; +} + .kbdShortcuts { background: $slate-950; border-top: 1px solid $slate-900; @@ -71,3 +84,37 @@ $padding-x: 24px; width: 1px; background: $slate-800; } + +.lineContext { + color: $slate-300; + background-color: #ffffff08; + padding: 8px 0; + border-radius: 4px; + margin-top: 8px; +} + +.lineContextCurrentLine { + background-color: $slate-800; + color: $slate-100; + font-weight: 600; + + .lineContextLineNo { + color: $slate-100; + border-right-color: $slate-600; + } +} + +.lineContextLineNo { + display: inline-block; + width: 60px; + text-align: right; + border-right: 1px solid $slate-950; + padding-right: 12px; + margin-right: 12px; +} + +.emptyMessage { + color: $slate-500; + text-align: center; + padding: 10px; +} diff --git a/packages/app/styles/app.scss b/packages/app/styles/app.scss index 7962c01b80..8263098e6b 100644 --- a/packages/app/styles/app.scss +++ b/packages/app/styles/app.scss @@ -74,6 +74,30 @@ html[class~='dark'] body { color: $gray-600; } +.text-slate-700 { + color: $slate-700; +} + +.text-slate-600 { + color: $slate-600; +} + +.text-slate-500 { + color: $slate-500; +} + +.text-slate-400 { + color: $slate-400; +} + +.text-slate-300 { + color: $slate-300; +} + +.text-slate-200 { + color: $slate-200; +} + .text-muted-hover { color: $text-muted; transition: color 0.2s ease; diff --git a/packages/app/styles/variables.scss b/packages/app/styles/variables.scss index 04a346adbe..cf69a0b270 100644 --- a/packages/app/styles/variables.scss +++ b/packages/app/styles/variables.scss @@ -34,7 +34,7 @@ $slate-600: #526077; $slate-700: #434e61; $slate-800: #3a4352; $slate-900: #343a46; -$slate-950: #1a1d23; +$slate-950: #25292e; /** Spacing