diff --git a/web/src/containers/Studio/internal/components/log/logChunk.tsx b/web/src/containers/Studio/internal/components/log/logChunk.tsx
new file mode 100644
index 0000000..e6d1a53
--- /dev/null
+++ b/web/src/containers/Studio/internal/components/log/logChunk.tsx
@@ -0,0 +1,153 @@
+import { useState, useEffect, SyntheticEvent } from 'react'
+import { Typography } from '@mui/material'
+import Highlight from 'react-highlight'
+import { ErrorOutline, Warning } from '@mui/icons-material'
+import ContentCopyIcon from '@mui/icons-material/ContentCopy'
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
+import { makeStyles } from '@mui/styles'
+import {
+ defaultChunkSize,
+ parseErrorsAndWarnings,
+ LogInstance,
+ clearErrorsAndWarningsHtmlWrapping
+} from '../../../../../utils'
+
+const useStyles: any = makeStyles((theme: any) => ({
+ expansionDescription: {
+ backgroundColor: '#fbfbfb',
+ border: '1px solid #e2e2e2',
+ borderRadius: '3px',
+ minHeight: '50px',
+ padding: '10px',
+ boxSizing: 'border-box',
+ whiteSpace: 'pre-wrap',
+ fontFamily: 'Monaco, Courier, monospace',
+ position: 'relative',
+ width: '100%',
+ [theme.breakpoints.down('sm')]: {
+ fontSize: theme.typography.pxToRem(12)
+ },
+ [theme.breakpoints.up('md')]: {
+ fontSize: theme.typography.pxToRem(16)
+ }
+ }
+}))
+
+interface LogChunkProps {
+ id: number
+ text: string
+ expanded: boolean
+ logLineCount: number
+ onClick: (evt: any, id: number) => void
+ scrollToLogInstance?: LogInstance
+}
+
+const LogChunk = (props: LogChunkProps) => {
+ const { id, text, logLineCount, scrollToLogInstance } = props
+
+ const classes = useStyles()
+ const [expanded, setExpanded] = useState(props.expanded)
+
+ useEffect(() => {
+ setExpanded(props.expanded)
+ }, [props.expanded])
+
+ useEffect(() => {
+ if (expanded && scrollToLogInstance) {
+ const { type, id } = scrollToLogInstance
+ const line = document.getElementById(`${type}_${id}`)
+ const logWrapper: HTMLDivElement | null =
+ document.querySelector(`#logWrapper`)
+ const logContainer: HTMLHeadElement | null =
+ document.querySelector(`#log_container`)
+
+ if (line && logWrapper && logContainer) {
+ const initialColor = line.style.color
+
+ line.style.backgroundColor = '#f6e30599'
+
+ line.scrollIntoView({ behavior: 'smooth', block: 'start' })
+
+ setTimeout(() => {
+ line.setAttribute('style', `color: ${initialColor};`)
+ }, 3000)
+ }
+ }
+ }, [expanded, scrollToLogInstance])
+
+ const { errors, warnings } = parseErrorsAndWarnings(text)
+
+ return (
+
props.onClick(evt, id)}>
+
+
+
+
+ {expanded ? text : ''}
+
+
+
+
+ )
+}
+
+export default LogChunk
diff --git a/web/src/containers/Studio/internal/components/log/logComponent.tsx b/web/src/containers/Studio/internal/components/log/logComponent.tsx
index 112f172..2eb7afc 100644
--- a/web/src/containers/Studio/internal/components/log/logComponent.tsx
+++ b/web/src/containers/Studio/internal/components/log/logComponent.tsx
@@ -5,8 +5,14 @@ import { Typography } from '@mui/material'
import { ListItemText } from '@mui/material'
import { makeStyles } from '@mui/styles'
import Highlight from 'react-highlight'
-import { LogObject } from '../../../../../utils'
+import { LogObject, defaultChunkSize } from '../../../../../utils'
import { RunTimeType } from '../../../../../context/appContext'
+import { splitIntoChunks, LogInstance } from '../../../../../utils'
+import LogChunk from './logChunk'
+import { useEffect, useState } from 'react'
+
+// TODO:
+// link to download log.log
const useStyles: any = makeStyles((theme: any) => ({
expansionDescription: {
@@ -37,32 +43,96 @@ interface LogComponentProps {
const LogComponent = (props: LogComponentProps) => {
const { log, selectedRunTime } = props
const logObject = log as LogObject
+ const logChunks = splitIntoChunks(logObject?.body || '')
+ const [logChunksState, setLogChunksState] = useState(
+ new Array(logChunks.length).fill(false)
+ )
+
+ const [scrollToLogInstance, setScrollToLogInstance] = useState()
+ const [oldestExpandedChunk, setOldestExpandedChunk] = useState(
+ logChunksState.length - 1
+ )
+ const maxOpenedChunks = 2
const classes = useStyles()
- const goToLogLine = (type: 'error' | 'warning', ind: number) => {
- const line = document.getElementById(`${type}_${ind}`)
+ const goToLogLine = (logInstance: LogInstance, ind: number) => {
+ let chunkNumber = 0
+
+ for (
+ let i = 0;
+ i <= Math.ceil(logObject.linesCount / defaultChunkSize);
+ i++
+ ) {
+ if (logInstance.line < (i + 1) * defaultChunkSize) {
+ chunkNumber = i
+
+ break
+ }
+ }
+
+ setLogChunksState((prevState) => {
+ const newState = [...prevState]
+ newState[chunkNumber] = true
+
+ const chunkToCollapse = getChunkToAutoCollapse()
+
+ if (chunkToCollapse !== undefined) {
+ newState[chunkToCollapse] = false
+ }
+
+ return newState
+ })
+
+ setScrollToLogInstance(logInstance)
+ }
+
+ useEffect(() => {
+ // INFO: expand the last chunk by default
+ setLogChunksState((prevState) => {
+ const lastChunk = prevState.length - 1
+
+ const newState = [...prevState]
+ newState[lastChunk] = true
+
+ return newState
+ })
+
+ setTimeout(() => {
+ scrollToTheBottom()
+ }, 100)
+ }, [])
+
+ // INFO: scroll to the bottom of the log
+ const scrollToTheBottom = () => {
const logWrapper: HTMLDivElement | null =
document.querySelector(`#logWrapper`)
- const logContainer: HTMLHeadElement | null =
- document.querySelector(`#log_container`)
- if (line && logWrapper && logContainer) {
- line.style.backgroundColor = '#f6e30599'
- logWrapper.scrollTop =
- line.offsetTop - logWrapper.offsetTop + logContainer.offsetTop
-
- setTimeout(() => {
- line.setAttribute('style', '')
- }, 3000)
+ if (logWrapper) {
+ logWrapper.scrollTop = logWrapper.scrollHeight
}
}
- const decodeHtml = (encodedString: string) => {
- const tempElement = document.createElement('textarea')
- tempElement.innerHTML = encodedString
+ const getChunkToAutoCollapse = () => {
+ const openedChunks = logChunksState
+ .map((chunkState: boolean, id: number) => (chunkState ? id : undefined))
+ .filter((chunk) => chunk !== undefined)
- return tempElement.value
+ if (openedChunks.length < maxOpenedChunks) return undefined
+ else {
+ const chunkToCollapse = oldestExpandedChunk
+ const newOldestChunk = openedChunks.filter(
+ (chunk) => chunk !== chunkToCollapse
+ )[0]
+
+ if (newOldestChunk !== undefined) {
+ setOldestExpandedChunk(newOldestChunk)
+
+ return chunkToCollapse
+ }
+
+ return undefined
+ }
}
return (
@@ -100,9 +170,19 @@ const LogComponent = (props: LogComponentProps) => {
logObject.errors.map((error, ind) => (
}
+ label={}
key={`error_${ind}`}
- onClick={() => goToLogLine('error', ind)}
+ onClick={() => {
+ setLogChunksState((prevState) => {
+ const newState = [...prevState]
+
+ newState[ind] = true
+
+ return newState
+ })
+
+ goToLogLine(error, ind)
+ }}
/>
))}
@@ -118,9 +198,19 @@ const LogComponent = (props: LogComponentProps) => {
logObject.warnings.map((warning, ind) => (
}
+ label={}
key={`warning_${ind}`}
- onClick={() => goToLogLine('warning', ind)}
+ onClick={() => {
+ setLogChunksState((prevState) => {
+ const newState = [...prevState]
+
+ newState[ind] = true
+
+ return newState
+ })
+
+ goToLogLine(warning, ind)
+ }}
/>
))}
@@ -129,15 +219,48 @@ const LogComponent = (props: LogComponentProps) => {
-
-
- {decodeHtml(logObject?.body || '')}
-
-
+ {Array.isArray(logChunks) ? (
+ logChunks.map((chunk: string, id: number) => (
+ {
+ setLogChunksState((prevState) => {
+ const newState = [...prevState]
+ const expand = !newState[chunkNumber]
+
+ newState[chunkNumber] = expand
+
+ if (expand) {
+ const chunkToCollapse = getChunkToAutoCollapse()
+
+ if (chunkToCollapse !== undefined) {
+ newState[chunkToCollapse] = false
+ }
+ }
+
+ return newState
+ })
+
+ setScrollToLogInstance(undefined)
+ }}
+ />
+ ))
+ ) : (
+
+
+ {logChunks}
+
+
+ )}
) : (
diff --git a/web/src/containers/Studio/internal/hooks/useEditor.ts b/web/src/containers/Studio/internal/hooks/useEditor.ts
index 04459df..5baba2d 100644
--- a/web/src/containers/Studio/internal/hooks/useEditor.ts
+++ b/web/src/containers/Studio/internal/hooks/useEditor.ts
@@ -178,7 +178,8 @@ const useEditor = ({
const log: LogObject = {
body: logLines.join(`\n`),
errors,
- warnings
+ warnings,
+ linesCount: logLines.length
}
setLog(log)
diff --git a/web/src/utils/log.ts b/web/src/utils/log.ts
index 4b8cc59..a3a7af9 100644
--- a/web/src/utils/log.ts
+++ b/web/src/utils/log.ts
@@ -1,21 +1,36 @@
+import { LogInstance } from './'
+
export const parseErrorsAndWarnings = (log: string) => {
const logLines = log.split('\n')
- const errorLines: string[] = []
- const warningLines: string[] = []
+ const errorLines: LogInstance[] = []
+ const warningLines: LogInstance[] = []
logLines.forEach((line: string, index: number) => {
// INFO: check if content in element starts with ERROR
if (/<.*>ERROR/gm.test(line)) {
const errorLine = line.substring(line.indexOf('E'), line.length - 1)
- errorLines.push(errorLine)
+
+ errorLines.push({
+ body: errorLine,
+ line: index,
+ type: 'error',
+ id: errorLines.length
+ })
}
// INFO: check if line starts with ERROR
else if (/^ERROR/gm.test(line)) {
- errorLines.push(line)
+ errorLines.push({
+ body: line,
+ line: index,
+ type: 'error',
+ id: errorLines.length
+ })
logLines[index] =
- `` +
+ `` +
logLines[index] +
''
}
@@ -23,12 +38,23 @@ export const parseErrorsAndWarnings = (log: string) => {
// INFO: check if content in element starts with WARNING
else if (/<.*>WARNING/gm.test(line)) {
const warningLine = line.substring(line.indexOf('W'), line.length - 1)
- warningLines.push(warningLine)
+
+ warningLines.push({
+ body: warningLine,
+ line: index,
+ type: 'warning',
+ id: warningLines.length
+ })
}
// INFO: check if line starts with WARNING
else if (/^WARNING/gm.test(line)) {
- warningLines.push(line)
+ warningLines.push({
+ body: line,
+ line: index,
+ type: 'warning',
+ id: warningLines.length
+ })
logLines[index] =
`` +
@@ -39,3 +65,38 @@ export const parseErrorsAndWarnings = (log: string) => {
return { errors: errorLines, warnings: warningLines, logLines }
}
+
+export const defaultChunkSize = 20000
+
+export const isTheLastChunk = (
+ lineCount: number,
+ chunkNumber: number,
+ chunkSize = defaultChunkSize
+) => {
+ if (lineCount <= chunkSize) return true
+
+ const chunksNumber = Math.ceil(lineCount / chunkSize)
+
+ return chunkNumber === chunksNumber
+}
+
+export const splitIntoChunks = (log: string, chunkSize = defaultChunkSize) => {
+ if (!log.length) return []
+
+ const logLines: string[] = log.split(`\n`)
+
+ if (logLines.length <= chunkSize) return log
+
+ const chunks: string[] = []
+
+ while (logLines.length) {
+ const chunk = logLines.splice(0, chunkSize)
+
+ chunks.push(chunk.join(`\n`))
+ }
+
+ return chunks
+}
+
+export const clearErrorsAndWarningsHtmlWrapping = (log: string) =>
+ log.replace(/^]*>/gm, '').replace(/<\/font>/gm, '')
diff --git a/web/src/utils/types.ts b/web/src/utils/types.ts
index b0e9b1d..8a10b5a 100644
--- a/web/src/utils/types.ts
+++ b/web/src/utils/types.ts
@@ -40,8 +40,17 @@ export interface TreeNode {
children: Array
}
+export interface LogInstance {
+ body: string
+ line: number
+ type: 'error' | 'warning'
+ id: number
+ ref?: any
+}
+
export interface LogObject {
body: string
- errors?: string[]
- warnings?: string[]
+ errors?: LogInstance[]
+ warnings?: LogInstance[]
+ linesCount: number
}