diff --git a/packages/common/src/components/Console/Console.stories.tsx b/packages/common/src/components/Console/Console.stories.tsx index c571448bb..936467938 100644 --- a/packages/common/src/components/Console/Console.stories.tsx +++ b/packages/common/src/components/Console/Console.stories.tsx @@ -26,7 +26,7 @@ const logs = [ "This is a test of an ERROR message. Also, this error message happens to be very very long. Super long. It's only purpose is to be super long. So long that we can test that the log container properly resizes itself and shows all of this super important, meaningful text that will help us understand if this log will be readable by the user.", severity: ConsoleLogSeverities.Error, }, -]; +].map((log, i) => ({ ...log, id: i.toString() })); const lotsOfLogs = [ { @@ -695,7 +695,7 @@ const lotsOfLogs = [ message: 'This is a test of an ERROR message.', severity: ConsoleLogSeverities.Error, }, -]; +].map((log, i) => ({ ...log, id: i.toString() })); const props: IProps = { logs, diff --git a/packages/common/src/components/Console/index.tsx b/packages/common/src/components/Console/index.tsx index f77669c90..fae6fb55d 100644 --- a/packages/common/src/components/Console/index.tsx +++ b/packages/common/src/components/Console/index.tsx @@ -3,8 +3,6 @@ import { withTheme } from 'styled-components'; import { Icon } from 'office-ui-fabric-react/lib/Icon'; import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox'; -import { List, ScrollToMode, IListProps } from 'office-ui-fabric-react/lib/List'; -import { FocusZone } from 'office-ui-fabric-react/lib/FocusZone'; import { Wrapper, @@ -18,6 +16,8 @@ import { } from './styles'; import HeaderFooterLayout from '../HeaderFooterLayout'; +const MAX_LOGS_SHOWN = 100; + export enum ConsoleLogSeverities { Info = 'info', Log = 'log', @@ -41,17 +41,13 @@ interface IState { } class Console extends React.Component { - private list: List; + private lastLog = React.createRef(); + state: IState = { shouldScrollToBottom: true, filterQuery: '' }; static defaultProps = { style: {}, }; - constructor(props: IPrivateProps) { - super(props); - this.state = { shouldScrollToBottom: true, filterQuery: '' }; - } - componentDidMount() { this.scrollToBottom(); } @@ -69,8 +65,8 @@ class Console extends React.Component { }); scrollToBottom() { - if (this.state.shouldScrollToBottom && this.list) { - this.list.scrollToIndex(this.props.logs.length); + if (this.state.shouldScrollToBottom && this.lastLog.current) { + this.lastLog.current.scrollIntoView(); } } @@ -78,8 +74,9 @@ class Console extends React.Component { const { theme, logs, clearLogs, style } = this.props; const items = logs + .slice(-1 * MAX_LOGS_SHOWN) // get the last X logs .filter(log => log.message.toLowerCase().includes(this.state.filterQuery)) - .map(({ severity, message }, i) => { + .map(({ id, severity, message }) => { const { backgroundColor, color, icon } = { [ConsoleLogSeverities.Log]: { backgroundColor: theme.white, @@ -104,7 +101,7 @@ class Console extends React.Component { }[severity]; return { - key: `${severity}-${i}`, + key: `${severity}-${id}}`, backgroundColor, color, icon, @@ -153,46 +150,30 @@ class Console extends React.Component { } > -
- -
+ {items.map(({ backgroundColor, color, key, icon, message }) => ( + + {icon ? ( + + ) : ( +
+ )} + {message} + + ))} +
); } - - private resolveList = (list: List): void => { - this.list = list; - }; - - private onRenderCell = ( - { key, backgroundColor, color, icon, message }: any, - index: number, - ): JSX.Element => { - return ( - - {icon ? ( - - ) : ( -
- )} - {message} - - ); - }; } export default withTheme(Console); diff --git a/packages/common/src/interfaces/logs.d.ts b/packages/common/src/interfaces/logs.d.ts index 0299936e2..55e1d289b 100644 --- a/packages/common/src/interfaces/logs.d.ts +++ b/packages/common/src/interfaces/logs.d.ts @@ -1,6 +1,7 @@ type ConsoleLogTypes = 'log' | 'info' | 'warn' | 'error'; interface ILogData { + id: string; message: string; severity: ConsoleLogTypes; } diff --git a/packages/runner/src/components/App/index.tsx b/packages/runner/src/components/App/index.tsx index af0901dae..7b30d53ad 100644 --- a/packages/runner/src/components/App/index.tsx +++ b/packages/runner/src/components/App/index.tsx @@ -31,6 +31,8 @@ const RefreshBar = props => ( /> ); +let logCount = 0; + interface IState { solution?: ISolution | null; lastRendered: number | null; @@ -89,16 +91,27 @@ export class App extends React.Component<{}, IState> { }); }; - addLog = (log: ILogData) => - this.setState({ logs: [...this.state.logs, log], isConsoleOpen: true }); + addLog = (log: { severity: ConsoleLogTypes; message: string }) => { + this.setState({ + logs: [...this.state.logs, { id: logCount.toString(), ...log }], + isConsoleOpen: true, + }); + logCount++; + }; clearLogs = () => this.setState({ logs: [] }); openConsole = () => this.setState({ isConsoleOpen: true }); closeConsole = () => this.setState({ isConsoleOpen: false }); onReceiveNewActiveSolution = (solution: ISolution | null) => { - if (solution !== null && this.state.solution) { - console.info(`Your snippet '${solution.name}' has been loaded.`); + if (solution !== null) { + if (!this.state.solution) { + console.info(`Your snippet "${solution.name}" has been loaded.`); + } else if (this.state.solution.id === solution.id) { + console.info(`Updating your snippet "${solution.name}".`); + } else { + console.info(`Switching to snippet "${solution.name}".`); + } } this.setState({ solution }); }; @@ -108,6 +121,7 @@ export class App extends React.Component<{}, IState> { this.setState({ solution: { ...this.state.solution, dateLastModified: Date.now() }, }); + console.info(`Your snippet '${this.state.solution.name}' has been reloaded.`); } };