Skip to content

Commit 4795aaa

Browse files
alan412lizlooney
andauthored
Add theme support (#144)
* Add theme support * Added space between if and ( * Added space between if and ( --------- Co-authored-by: Liz Looney <[email protected]>
1 parent fa97dda commit 4795aaa

File tree

7 files changed

+350
-60
lines changed

7 files changed

+350
-60
lines changed
File renamed without changes.

src/App.tsx

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ import * as Antd from 'antd';
2323
import '@ant-design/v5-patch-for-react-19';
2424

2525
import * as Blockly from 'blockly/core';
26-
import {pythonGenerator} from 'blockly/python';
26+
import { pythonGenerator } from 'blockly/python';
2727

2828
import Header from './reactComponents/Header';
2929
import * as Menu from './reactComponents/Menu';
3030
import CodeDisplay from './reactComponents/CodeDisplay';
31-
import BlocklyComponent, {BlocklyComponentType} from './reactComponents/BlocklyComponent';
31+
import BlocklyComponent, { BlocklyComponentType } from './reactComponents/BlocklyComponent';
3232
import ToolboxSettingsModal from './reactComponents/ToolboxSettings';
3333
import * as Tabs from './reactComponents/Tabs';
34-
import {TabType } from './types/TabType';
34+
import { TabType } from './types/TabType';
3535

36-
import {createGeneratorContext, GeneratorContext} from './editor/generator_context';
36+
import { createGeneratorContext, GeneratorContext } from './editor/generator_context';
3737
import * as editor from './editor/editor';
38-
import {extendedPythonGenerator} from './editor/extended_python_generator';
38+
import { extendedPythonGenerator } from './editor/extended_python_generator';
3939

4040
import * as commonStorage from './storage/common_storage';
4141
import * as clientSideStorage from './storage/client_side_storage';
@@ -46,6 +46,7 @@ import { initialize as initializePythonBlocks } from './blocks/utils/python';
4646
import * as ChangeFramework from './blocks/utils/change_framework'
4747
import { mutatorOpenListener } from './blocks/mrc_param_container'
4848
import { TOOLBOX_UPDATE_EVENT } from './blocks/mrc_mechanism_component_holder';
49+
import { antdThemeFromString } from './reactComponents/ThemeModal';
4950

5051
/** Storage key for shown toolbox categories. */
5152
const SHOWN_TOOLBOX_CATEGORIES_KEY = 'shownPythonToolboxCategories';
@@ -98,6 +99,7 @@ const App: React.FC = (): React.JSX.Element => {
9899
const [triggerPythonRegeneration, setTriggerPythonRegeneration] = React.useState(0);
99100
const [leftCollapsed, setLeftCollapsed] = React.useState(false);
100101
const [rightCollapsed, setRightCollapsed] = React.useState(false);
102+
const [theme, setTheme] = React.useState('dark');
101103

102104

103105
const blocksEditor = React.useRef<editor.Editor | null>(null);
@@ -264,7 +266,7 @@ const App: React.FC = (): React.JSX.Element => {
264266
// Add event listener for toolbox updates
265267
React.useEffect(() => {
266268
window.addEventListener(TOOLBOX_UPDATE_EVENT, handleToolboxUpdateRequest);
267-
269+
268270
return () => {
269271
window.removeEventListener(TOOLBOX_UPDATE_EVENT, handleToolboxUpdateRequest);
270272
};
@@ -312,8 +314,8 @@ const App: React.FC = (): React.JSX.Element => {
312314
if (currentModule && blocklyComponent.current && generatorContext.current) {
313315
const blocklyWorkspace = blocklyComponent.current.getBlocklyWorkspace();
314316
setGeneratedCode(extendedPythonGenerator.mrcWorkspaceToCode(
315-
blocklyWorkspace,
316-
generatorContext.current
317+
blocklyWorkspace,
318+
generatorContext.current
317319
));
318320
} else {
319321
setGeneratedCode('');
@@ -336,26 +338,14 @@ const App: React.FC = (): React.JSX.Element => {
336338
}
337339
}, [project]);
338340

339-
const {Sider,Content} = Antd.Layout;
341+
const { Sider, Content } = Antd.Layout;
340342

341343
return (
342344
<Antd.ConfigProvider
343-
theme={{
344-
algorithm: Antd.theme.darkAlgorithm,
345-
components: {
346-
Layout: {
347-
headerBg: '#000000', // This is only for dark mode
348-
siderBg: '#000000', // This is only for dark mode
349-
triggerBg: '#000000', // This is only for dark mode
350-
},
351-
Menu:{
352-
darkItemBg: '#000000', // This is only for dark mode
353-
}
354-
},
355-
}}
345+
theme={antdThemeFromString(theme)}
356346
>
357347
{contextHolder}
358-
<Antd.Layout style={{height: FULL_VIEWPORT_HEIGHT}}>
348+
<Antd.Layout style={{ height: FULL_VIEWPORT_HEIGHT }}>
359349
<Header
360350
alertErrorMessage={alertErrorMessage}
361351
setAlertErrorMessage={setAlertErrorMessage}
@@ -379,6 +369,7 @@ const App: React.FC = (): React.JSX.Element => {
379369
project={project}
380370
setProject={setProject}
381371
openWPIToolboxSettings={() => setToolboxSettingsModalIsOpen(true)}
372+
setTheme={setTheme}
382373
/>
383374
</Sider>
384375
<Antd.Layout>
@@ -395,7 +386,10 @@ const App: React.FC = (): React.JSX.Element => {
395386
/>
396387
<Antd.Layout>
397388
<Content>
398-
<BlocklyComponent ref={blocklyComponent} />
389+
<BlocklyComponent
390+
theme={theme}
391+
ref={blocklyComponent}
392+
/>
399393
</Content>
400394
<Sider
401395
collapsible
@@ -409,6 +403,7 @@ const App: React.FC = (): React.JSX.Element => {
409403
generatedCode={generatedCode}
410404
messageApi={messageApi}
411405
setAlertErrorMessage={setAlertErrorMessage}
406+
theme={theme}
412407
/>
413408
</Sider>
414409
</Antd.Layout>

src/reactComponents/BlocklyComponent.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
import * as React from 'react';
2222
import * as Blockly from 'blockly/core';
2323
import * as locale from 'blockly/msg/en';
24-
import * as MrcTheme from '../themes/mrc_theme_dark';
24+
import * as MrcDarkTheme from '../themes/mrc_theme_dark';
25+
import * as MrcLightTheme from '../themes/mrc_theme_light';
2526
import {pluginInfo as HardwareConnectionsPluginInfo} from '../blocks/utils/connection_checker';
2627

2728
import 'blockly/blocks'; // Includes standard blocks like controls_if, logic_compare, etc.
@@ -31,6 +32,11 @@ export interface BlocklyComponentType {
3132
getBlocklyWorkspace: () => Blockly.WorkspaceSvg;
3233
}
3334

35+
/** Interface for props passed to the BlocklyComponent. */
36+
export interface BlocklyComponentProps {
37+
theme: string;
38+
}
39+
3440
/** Grid spacing for the Blockly workspace. */
3541
const GRID_SPACING = 20;
3642

@@ -68,14 +74,25 @@ const WORKSPACE_STYLE: React.CSSProperties = {
6874
* React component that renders a Blockly workspace with proper initialization,
6975
* cleanup, and resize handling.
7076
*/
71-
const BlocklyComponent = React.forwardRef<BlocklyComponentType | null>(
72-
(_, ref): React.JSX.Element => {
77+
const BlocklyComponent = React.forwardRef<BlocklyComponentType | null, BlocklyComponentProps>(
78+
({ theme }, ref): React.JSX.Element => {
7379
const blocklyDiv = React.useRef<HTMLDivElement | null>(null);
7480
const workspaceRef = React.useRef<Blockly.WorkspaceSvg | null>(null);
7581

82+
const getBlocklyTheme = (): Blockly.Theme => {
83+
if (theme === 'dark' || theme === 'compact-dark') {
84+
return MrcDarkTheme.theme;
85+
}
86+
if (theme === 'light' || theme === 'compact') {
87+
return MrcLightTheme.theme;
88+
}
89+
// Default to light theme if unknown
90+
return MrcLightTheme.theme;
91+
};
92+
7693
/** Creates the Blockly workspace configuration object. */
7794
const createWorkspaceConfig = (): Blockly.BlocklyOptions => ({
78-
theme: MrcTheme.theme,
95+
theme: getBlocklyTheme(),
7996
horizontalLayout: false, // Forces vertical layout for the workspace
8097
// Start with an empty (but not null) toolbox. It will be replaced later.
8198
toolbox: {
@@ -168,6 +185,14 @@ const BlocklyComponent = React.forwardRef<BlocklyComponentType | null>(
168185
return cleanupWorkspace;
169186
}, []);
170187

188+
// Update theme when theme prop changes
189+
React.useEffect(() => {
190+
if (workspaceRef.current) {
191+
const newTheme = getBlocklyTheme();
192+
workspaceRef.current.setTheme(newTheme);
193+
}
194+
}, [theme]);
195+
171196
// Handle workspace resize
172197
React.useEffect(() => {
173198
return setupResizeObserver();

src/reactComponents/CodeDisplay.tsx

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,29 @@
2020
*/
2121
import * as Antd from 'antd';
2222
import * as React from 'react';
23-
import {CopyOutlined as CopyIcon} from '@ant-design/icons';
24-
import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter';
25-
import {dracula} from 'react-syntax-highlighter/dist/esm/styles/prism';
23+
import { CopyOutlined as CopyIcon } from '@ant-design/icons';
24+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
25+
import { dracula, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
2626

27-
import type {MessageInstance} from 'antd/es/message/interface';
27+
import type { MessageInstance } from 'antd/es/message/interface';
2828

2929
/** Function type for setting string values. */
3030
type StringFunction = (input: string) => void;
3131

3232
/** Props for the CodeDisplay component. */
3333
interface CodeDisplayProps {
3434
generatedCode: string;
35+
theme: string;
3536
messageApi: MessageInstance;
3637
setAlertErrorMessage: StringFunction;
3738
}
3839

39-
/** Background color for the syntax highlighter. */
40-
const SYNTAX_HIGHLIGHTER_BACKGROUND = '#333';
41-
4240
/** Full dimensions style for the syntax highlighter. */
4341
const FULL_SIZE_STYLE = {
4442
width: '100%',
4543
height: '100%',
4644
};
4745

48-
/** Header text color. */
49-
const HEADER_TEXT_COLOR = '#fff';
50-
51-
/** Button text color. */
52-
const BUTTON_TEXT_COLOR = 'white';
53-
5446
/** Success message for copy operation. */
5547
const COPY_SUCCESS_MESSAGE = 'Copy completed successfully.';
5648

@@ -62,37 +54,52 @@ const COPY_ERROR_MESSAGE_PREFIX = 'Could not copy code: ';
6254
* Shows generated Python code in a dark theme with a copy button.
6355
*/
6456
export default function CodeDisplay(props: CodeDisplayProps): React.JSX.Element {
57+
const syntaxHighligherFromTheme = (theme: string) => {
58+
switch (theme) {
59+
case 'dark':
60+
case 'compact-dark':
61+
return dracula;
62+
case 'light':
63+
case 'compact':
64+
return oneLight;
65+
default:
66+
return dracula; // Default to dracula if theme is unknown
67+
}
68+
}
69+
70+
const { token } = Antd.theme.useToken();
71+
const syntaxStyle = syntaxHighligherFromTheme(props.theme);
72+
6573
/** Handles copying the generated code to clipboard. */
6674
const handleCopyCode = (): void => {
6775
navigator.clipboard.writeText(props.generatedCode).then(
68-
() => {
69-
props.messageApi.open({
70-
type: 'success',
71-
content: COPY_SUCCESS_MESSAGE,
72-
});
73-
},
74-
(err) => {
75-
props.setAlertErrorMessage(COPY_ERROR_MESSAGE_PREFIX + err);
76-
}
76+
() => {
77+
props.messageApi.open({
78+
type: 'success',
79+
content: COPY_SUCCESS_MESSAGE,
80+
});
81+
},
82+
(err) => {
83+
props.setAlertErrorMessage(COPY_ERROR_MESSAGE_PREFIX + err);
84+
}
7785
);
7886
};
7987

8088
/** Creates the custom style object for the syntax highlighter. */
8189
const getSyntaxHighlighterStyle = (): React.CSSProperties => ({
82-
backgroundColor: SYNTAX_HIGHLIGHTER_BACKGROUND,
90+
backgroundColor: token.colorBgContainer,
8391
...FULL_SIZE_STYLE,
8492
});
8593

8694
/** Renders the header section with title and copy button. */
8795
const renderHeader = (): React.JSX.Element => (
8896
<Antd.Space>
89-
<h3 style={{color: HEADER_TEXT_COLOR, margin: 0}}>Code</h3>
97+
<Antd.Typography.Title level={3}>Code</Antd.Typography.Title>
9098
<Antd.Tooltip title="Copy">
9199
<Antd.Button
92100
icon={<CopyIcon />}
93101
size="small"
94102
onClick={handleCopyCode}
95-
style={{color: BUTTON_TEXT_COLOR}}
96103
/>
97104
</Antd.Tooltip>
98105
</Antd.Space>
@@ -102,7 +109,7 @@ export default function CodeDisplay(props: CodeDisplayProps): React.JSX.Element
102109
const renderCodeBlock = (): React.JSX.Element => (
103110
<SyntaxHighlighter
104111
language="python"
105-
style={dracula}
112+
style={syntaxStyle}
106113
customStyle={getSyntaxHighlighterStyle()}
107114
>
108115
{props.generatedCode}

src/reactComponents/Header.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ const TEXT_PADDING_LEFT = 20;
4949
* and any error messages.
5050
*/
5151
export default function Header(props: HeaderProps): React.JSX.Element {
52+
const { token } = Antd.theme.useToken();
53+
54+
const isDarkTheme = token.colorBgLayout === '#000000';
55+
5256
/** Handles clearing the error message. */
5357
const handleClearError = (): void => {
5458
props.setAlertErrorMessage('');
@@ -76,17 +80,16 @@ export default function Header(props: HeaderProps): React.JSX.Element {
7680
};
7781

7882
return (
79-
<Antd.Flex vertical style={{background: '#000'}}>
83+
<Antd.Flex vertical>
8084
<Antd.Flex style={{alignItems: 'center'}}>
8185
<img
8286
height={LOGO_HEIGHT}
8387
style={{objectFit: 'contain'}}
84-
src="/FIRST_HorzRGB_reverse.png"
88+
src={isDarkTheme ? "/FIRST_HorzRGB_reverse.png" : "/FIRST_HorzRGB.png"}
8589
alt="FIRST Logo"
8690
/>
8791
<Antd.Typography
8892
style={{
89-
color: 'white',
9093
paddingLeft: TEXT_PADDING_LEFT,
9194
fontSize: TEXT_FONT_SIZE,
9295
fontWeight: TITLE_FONT_WEIGHT,
@@ -96,7 +99,6 @@ export default function Header(props: HeaderProps): React.JSX.Element {
9699
</Antd.Typography>
97100
<Antd.Typography
98101
style={{
99-
color: 'white',
100102
paddingLeft: TEXT_PADDING_LEFT,
101103
fontSize: TEXT_FONT_SIZE,
102104
fontWeight: 'normal',

0 commit comments

Comments
 (0)