Skip to content

Commit

Permalink
Build log added to debugger pane
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismaltby committed Apr 23, 2024
1 parent eca6ebe commit d70b4a1
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 63 deletions.
11 changes: 9 additions & 2 deletions src/components/app/AppToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import l10n from "shared/lib/lang/l10n";
import editorActions from "store/features/editor/editorActions";
import navigationActions from "store/features/navigation/navigationActions";
import electronActions from "store/features/electron/electronActions";
import debuggerActions from "store/features/debugger/debuggerActions";
import settingsActions from "store/features/settings/settingsActions";
import buildGameActions, {
BuildType,
} from "store/features/buildGame/buildGameActions";
Expand Down Expand Up @@ -151,6 +153,11 @@ const AppToolbar: FC = () => {
dispatch(electronActions.openFolder("/"));
}, [dispatch]);

const openBuildLog = useCallback(() => {
dispatch(settingsActions.editSettings({ debuggerEnabled: true }));
dispatch(debuggerActions.setIsLogOpen(true));
}, [dispatch]);

// Handle focusing search when pressing "/"
const onKeyDown = useCallback((e: KeyboardEvent) => {
if (
Expand Down Expand Up @@ -264,13 +271,13 @@ const AppToolbar: FC = () => {
</DropdownButton>
<FixedSpacer width={10} />
{cancelling ? (
<Button title={l10n("BUILD_CANCELLING")} onClick={setSection("build")}>
<Button title={l10n("BUILD_CANCELLING")} onClick={openBuildLog}>
<DotsIcon />
</Button>
) : (
<Button
title={l10n("TOOLBAR_RUN")}
onClick={running ? setSection("build") : onRun}
onClick={running ? openBuildLog : onRun}
>
{running ? <LoadingIcon /> : <PlayIcon />}
</Button>
Expand Down
140 changes: 140 additions & 0 deletions src/components/debugger/DebuggerBuildLog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React, { useCallback, useEffect, useRef } from "react";
import styled from "styled-components";
import { Button } from "ui/buttons/Button";
import l10n from "shared/lib/lang/l10n";
import consoleActions from "store/features/console/consoleActions";
import buildGameActions from "store/features/buildGame/buildGameActions";
import { FlexGrow } from "ui/spacing/Spacing";
import { useAppDispatch, useAppSelector } from "store/hooks";

const PIN_TO_BOTTOM_RANGE = 100;

const Wrapper = styled.div`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: auto;
font-size: 11px;
`;

const Terminal = styled.div`
flex-grow: 1;
background: #111;
color: #fff;
padding: 10px;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
overflow: auto;
user-select: text;
`;

const ButtonToolbar = styled.div`
display: flex;
flex-direction: row;
align-items: center;
padding: 10px;
> * {
height: 24px;
line-height: 24px;
}
> * ~ * {
margin-left: 10px;
}
`;

const DebuggerBuildLog = () => {
const scrollRef = useRef<HTMLDivElement>(null);
const dispatch = useAppDispatch();

const output = useAppSelector((state) => state.console.output);
const warnings = useAppSelector((state) => state.console.warnings);
const status = useAppSelector((state) => state.console.status);

// Only show the latest 500 lines during build
// show full output on complete
const outputLines = status === "complete" ? output : output.slice(-500);

const onDeleteCache = useCallback(() => {
dispatch(buildGameActions.deleteBuildCache());
}, [dispatch]);

const onRun = useCallback(() => {
dispatch(buildGameActions.buildGame());
}, [dispatch]);

const onClear = useCallback(() => {
dispatch(consoleActions.clearConsole());
}, [dispatch]);

useEffect(() => {
// Pin scroll to bottom of console as new lines arrive if currently near bottom of scroll anyway
const scrollEl = scrollRef.current;
if (scrollEl) {
if (
scrollEl.scrollTop >
scrollEl.scrollHeight - scrollEl.clientHeight - PIN_TO_BOTTOM_RANGE
) {
scrollEl.scrollTop = scrollEl.scrollHeight;
}
}
}, [output]);

useEffect(() => {
// Pin scroll to bottom of console on initial load
const scrollEl = scrollRef.current;
if (scrollEl) {
scrollEl.scrollTop = scrollEl.scrollHeight;
}
}, []);

return (
<Wrapper>
<Terminal ref={scrollRef}>
{outputLines.map((out, index) => (
<div
// eslint-disable-next-line react/no-array-index-key
key={index}
style={{ color: out.type === "err" ? "orange" : "white" }}
>
{out.text}
</div>
))}
{status === "cancelled" && (
<div style={{ color: "orange" }}>{l10n("BUILD_CANCELLING")}...</div>
)}
{status === "complete" && warnings.length > 0 && (
<div>
<br />
Warnings:
{warnings.map((out, index) => (
// eslint-disable-next-line react/no-array-index-key
<div key={index} style={{ color: "orange" }}>
- {out.text}
</div>
))}
</div>
)}
</Terminal>
<ButtonToolbar>
{status === "running" ? (
<Button onClick={onRun}>{l10n("BUILD_CANCEL")}</Button>
) : (
<>
<Button onClick={onRun}>{l10n("BUILD_RUN")}</Button>
<Button onClick={onDeleteCache}>
{l10n("BUILD_EMPTY_BUILD_CACHE")}
</Button>
</>
)}
<FlexGrow />
<Button onClick={onClear}>{l10n("BUILD_CLEAR")}</Button>
</ButtonToolbar>
</Wrapper>
);
};

export default DebuggerBuildLog;
83 changes: 53 additions & 30 deletions src/components/debugger/DebuggerControls.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import React, { useCallback, useEffect } from "react";
import API from "renderer/lib/api";
import l10n from "shared/lib/lang/l10n";
import { useAppSelector } from "store/hooks";
import l10n, { L10NKey } from "shared/lib/lang/l10n";
import { useAppDispatch, useAppSelector } from "store/hooks";
import { Button } from "ui/buttons/Button";
import { PlayStartIcon, PauseIcon, NextIcon, StepIcon } from "ui/icons/Icons";
import { FixedSpacer } from "ui/spacing/Spacing";
import debuggerActions from "store/features/debugger/debuggerActions";
import settingsActions from "store/features/settings/settingsActions";

const DebuggerControls = () => {
const dispatch = useAppDispatch();
const initialized = useAppSelector((state) => state.debug.initialized);
const isPaused = useAppSelector((state) => state.debug.isPaused);
const isLogOpen = useAppSelector((state) => state.debug.isLogOpen);
const debuggerEnabled = useAppSelector(
(state) => state.project.present.settings.debuggerEnabled
);

const onPlayPause = useCallback(() => {
if (isPaused) {
Expand All @@ -26,6 +33,15 @@ const DebuggerControls = () => {
API.debugger.stepFrame();
}, []);

const onToggleBuildLog = useCallback(() => {
if (!debuggerEnabled) {
dispatch(settingsActions.editSettings({ debuggerEnabled: true }));
dispatch(debuggerActions.setIsLogOpen(true));
} else {
dispatch(debuggerActions.setIsLogOpen(!isLogOpen));
}
}, [debuggerEnabled, dispatch, isLogOpen]);

const onKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === "F8") {
Expand All @@ -46,39 +62,46 @@ const DebuggerControls = () => {
};
}, [onKeyDown]);

if (!initialized) {
return null;
}

return (
<>
{initialized && (
<>
<Button
size="small"
variant="transparent"
onClick={onPlayPause}
title={isPaused ? l10n("FIELD_RESUME") : l10n("FIELD_PAUSE")}
>
{isPaused ? <PlayStartIcon /> : <PauseIcon />}
</Button>
<FixedSpacer width={5} />
<Button
size="small"
variant="transparent"
disabled={!isPaused}
onClick={isPaused ? onStep : undefined}
title={l10n("FIELD_STEP")}
>
<StepIcon />
</Button>
<Button
size="small"
variant="transparent"
disabled={!isPaused}
onClick={isPaused ? onStepFrame : undefined}
title={l10n("FIELD_STEP_FRAME")}
>
<NextIcon />
</Button>
</>
)}
<FixedSpacer width={10} />
<Button
size="small"
variant="transparent"
onClick={onPlayPause}
title={isPaused ? l10n("FIELD_RESUME") : l10n("FIELD_PAUSE")}
>
{isPaused ? <PlayStartIcon /> : <PauseIcon />}
</Button>
<FixedSpacer width={5} />

<Button
size="small"
variant="transparent"
disabled={!isPaused}
onClick={isPaused ? onStep : undefined}
title={l10n("FIELD_STEP")}
>
<StepIcon />
</Button>
<Button
size="small"
variant="transparent"
disabled={!isPaused}
onClick={isPaused ? onStepFrame : undefined}
title={l10n("FIELD_STEP_FRAME")}
variant={isLogOpen && debuggerEnabled ? "primary" : "transparent"}
onClick={onToggleBuildLog}
>
<NextIcon />
{l10n("FIELD_BUILD_LOG" as L10NKey)}
</Button>
</>
);
Expand Down
11 changes: 11 additions & 0 deletions src/components/debugger/DebuggerPanes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import DebuggerPausedPane from "components/debugger/DebuggerPausedPane";
import buildGameActions from "store/features/buildGame/buildGameActions";
import { Button } from "ui/buttons/Button";
import l10n from "shared/lib/lang/l10n";
import DebuggerBuildLog from "components/debugger/DebuggerBuildLog";

const COL1_WIDTH = 290;
const COL2_WIDTH = 350;
Expand Down Expand Up @@ -61,6 +62,8 @@ const DebuggerPanes = () => {

const initialized = useAppSelector((state) => state.debug.initialized);
const buildStatus = useAppSelector((state) => state.console.status);
const isLogOpen = useAppSelector((state) => state.debug.isLogOpen);

const running = buildStatus === "running";

const onRun = useCallback(() => {
Expand All @@ -81,6 +84,14 @@ const DebuggerPanes = () => {
? 2
: 1;

if (isLogOpen) {
return (
<Wrapper ref={wrapperEl}>
<DebuggerBuildLog />
</Wrapper>
);
}

return (
<Wrapper ref={wrapperEl}>
{!initialized && (
Expand Down
30 changes: 13 additions & 17 deletions src/components/pages/WorldPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { useAppDispatch, useAppSelector } from "store/hooks";
import { SplitPaneHeader } from "ui/splitpane/SplitPaneHeader";
import l10n from "shared/lib/lang/l10n";
import DebuggerPanes from "components/debugger/DebuggerPanes";
import API from "renderer/lib/api";
import DebuggerControls from "components/debugger/DebuggerControls";

const Wrapper = styled.div`
Expand Down Expand Up @@ -127,22 +126,19 @@ const WorldPage = () => {
}
}, [debuggerPaneHeight, dispatch, setDebuggerPaneSize, windowHeight]);

// Keep track of if debugger is visible
// If not and it has become visible open to default height
const debugOpenRef = useRef(debuggerEnabled);
useEffect(() => {
const unsubscribe = API.events.debugger.data.subscribe((_, packet) => {
switch (packet.action) {
case "initialized": {
if (debuggerPaneHeight <= 30) {
setDebuggerPaneSize(windowHeight * 0.5);
}
unsubscribe();
break;
}
}
});
return () => {
unsubscribe();
};
}, [debuggerPaneHeight, setDebuggerPaneSize, windowHeight]);
if (
debuggerEnabled &&
debugOpenRef.current !== debuggerEnabled &&
debuggerPaneHeight <= 30
) {
setDebuggerPaneSize(windowHeight * 0.5);
}
debugOpenRef.current = debuggerEnabled;
}, [debuggerEnabled, debuggerPaneHeight, setDebuggerPaneSize, windowHeight]);

useEffect(() => {
prevWindowWidthRef.current = windowWidth;
Expand Down Expand Up @@ -283,7 +279,7 @@ const WorldPage = () => {
<SplitPaneHeader
onToggle={toggleDebuggerPane}
collapsed={debuggerPaneHeight <= 30}
buttons={debuggerPaneHeight > 30 && <DebuggerControls />}
buttons={<DebuggerControls />}
>
{l10n("FIELD_DEBUGGER")}
</SplitPaneHeader>
Expand Down
1 change: 1 addition & 0 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,7 @@
"FIELD_MANUAL": "Manual",
"FIELD_PREVIEW_AS_MONO": "Preview as Monochrome",
"FIELD_MONO_OVERRIDE_DESC": "This image is using automatic palettes with a monochrome override defined at \"{tilesFilename}\" which will determine the tile data used, while \"{filename}\" will determine the colors",
"FIELD_BUILD_LOG": "Build Log",

"// 7": "Asset Viewer ---------------------------------------------",
"ASSET_SEARCH": "Search...",
Expand Down
Loading

0 comments on commit d70b4a1

Please sign in to comment.