Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions public/locales/en/translation.json

This file was deleted.

71 changes: 57 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import * as ChangeFramework from './blocks/utils/change_framework'
import { mutatorOpenListener } from './blocks/mrc_param_container'
import { TOOLBOX_UPDATE_EVENT } from './blocks/mrc_mechanism_component_holder';
import { antdThemeFromString } from './reactComponents/ThemeModal';
import { useTranslation } from 'react-i18next';

/** Storage key for shown toolbox categories. */
const SHOWN_TOOLBOX_CATEGORIES_KEY = 'shownPythonToolboxCategories';
Expand Down Expand Up @@ -86,6 +87,8 @@ const LAYOUT_BACKGROUND_COLOR = '#0F0';
* project management, and user interface layout.
*/
const App: React.FC = (): React.JSX.Element => {
const { t, i18n } = useTranslation();

const [alertErrorMessage, setAlertErrorMessage] = React.useState('');
const [storage, setStorage] = React.useState<commonStorage.Storage | null>(null);
const [currentModule, setCurrentModule] = React.useState<commonStorage.Module | null>(null);
Expand All @@ -101,7 +104,6 @@ const App: React.FC = (): React.JSX.Element => {
const [rightCollapsed, setRightCollapsed] = React.useState(false);
const [theme, setTheme] = React.useState('dark');


const blocksEditor = React.useRef<editor.Editor | null>(null);
const generatorContext = React.useRef<GeneratorContext | null>(null);
const blocklyComponent = React.useRef<BlocklyComponentType | null>(null);
Expand Down Expand Up @@ -232,7 +234,7 @@ const App: React.FC = (): React.JSX.Element => {
const tabs: Tabs.TabItem[] = [
{
key: projectData.robot.modulePath,
title: 'Robot',
title: t('ROBOT'),
type: TabType.ROBOT,
},
];
Expand Down Expand Up @@ -261,7 +263,7 @@ const App: React.FC = (): React.JSX.Element => {
if (blocksEditor.current && currentModule) {
blocksEditor.current.updateToolbox(shownPythonToolboxCategories);
}
}, [currentModule, shownPythonToolboxCategories]);
}, [currentModule, shownPythonToolboxCategories, i18n.language]);

// Add event listener for toolbox updates
React.useEffect(() => {
Expand Down Expand Up @@ -290,7 +292,31 @@ const App: React.FC = (): React.JSX.Element => {
if (blocksEditor.current) {
blocksEditor.current.loadModuleBlocks(currentModule);
}
}, [currentModule]);
}, [currentModule]);

const setupWorkspace = (newWorkspace: Blockly.WorkspaceSvg) => {
if (!blocklyComponent.current || !storage) {
return;
}
// Recreate workspace when Blockly component is ready
ChangeFramework.setup(newWorkspace);
newWorkspace.addChangeListener(mutatorOpenListener);
newWorkspace.addChangeListener(handleBlocksChanged);
generatorContext.current = createGeneratorContext();

if (currentModule) {
generatorContext.current.setModule(currentModule);
}

blocksEditor.current = new editor.Editor(newWorkspace, generatorContext.current, storage);

// Set the current module in the editor after creating it
if (currentModule) {
blocksEditor.current.loadModuleBlocks(currentModule);
}

blocksEditor.current.updateToolbox(shownPythonToolboxCategories);
};

// Initialize Blockly workspace and editor when component and storage are ready
React.useEffect(() => {
Expand All @@ -300,17 +326,8 @@ const App: React.FC = (): React.JSX.Element => {

const blocklyWorkspace = blocklyComponent.current.getBlocklyWorkspace();
if (blocklyWorkspace) {
ChangeFramework.setup(blocklyWorkspace);
blocklyWorkspace.addChangeListener(mutatorOpenListener);
blocklyWorkspace.addChangeListener(handleBlocksChanged);
setupWorkspace(blocklyWorkspace);
}

generatorContext.current = createGeneratorContext();
if (currentModule) {
generatorContext.current.setModule(currentModule);
}

blocksEditor.current = new editor.Editor(blocklyWorkspace, generatorContext.current, storage);
}, [blocklyComponent, storage]);

// Generate code when module or regeneration trigger changes
Expand Down Expand Up @@ -342,6 +359,31 @@ const App: React.FC = (): React.JSX.Element => {
}
}, [project]);

// Handle language changes with automatic saving
React.useEffect(() => {
const handleLanguageChange = async () => {
// Save current blocks before language change
if (currentModule && areBlocksModified()) {
try {
await saveBlocks();
} catch (e) {
console.error('Failed to save blocks before language change:', e);
}
}

// Update toolbox after language change
if (blocksEditor.current) {
blocksEditor.current.updateToolbox(shownPythonToolboxCategories);
}
};

i18n.on('languageChanged', handleLanguageChange);

return () => {
i18n.off('languageChanged', handleLanguageChange);
};
}, [currentModule, shownPythonToolboxCategories, i18n]);

const { Sider, Content } = Antd.Layout;

return (
Expand Down Expand Up @@ -392,6 +434,7 @@ const App: React.FC = (): React.JSX.Element => {
<Content>
<BlocklyComponent
theme={theme}
onWorkspaceRecreated={setupWorkspace}
ref={blocklyComponent}
/>
</Content>
Expand Down
12 changes: 7 additions & 5 deletions src/blocks/mrc_call_python_function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,12 +421,14 @@ const CALL_PYTHON_FUNCTION = {
returnType: this.mrcReturnType,
args: [],
};
this.mrcArgs.forEach((arg) => {
extraState.args.push({
'name': arg.name,
'type': arg.type,
if (this.mrcArgs){
this.mrcArgs.forEach((arg) => {
extraState.args.push({
'name': arg.name,
'type': arg.type,
});
});
});
}
if (this.mrcTooltip) {
extraState.tooltip = this.mrcTooltip;
}
Expand Down
1 change: 1 addition & 0 deletions src/blocks/mrc_class_method_def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const CLASS_METHOD_DEF = {
this.mrcParameters = [];
this.setPreviousStatement(false);
this.setNextStatement(false);
this.updateBlock_();
},
/**
* Returns the state of this block as a JSON serializable object.
Expand Down
14 changes: 8 additions & 6 deletions src/blocks/mrc_component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const COMPONENT = {
this.setStyle(MRC_STYLE_COMPONENTS);
this.appendDummyInput()
.appendField(new Blockly.FieldTextInput(''), FIELD_NAME)
.appendField('of type')
.appendField(Blockly.Msg.OF_TYPE)
.appendField(createFieldNonEditableText(''), FIELD_TYPE);
this.setPreviousStatement(true, OUTPUT_NAME);
this.setNextStatement(true, OUTPUT_NAME);
Expand All @@ -82,12 +82,14 @@ const COMPONENT = {
const extraState: ComponentExtraState = {
};
extraState.params = [];
this.mrcArgs.forEach((arg) => {
extraState.params!.push({
'name': arg.name,
'type': arg.type,
if (this.mrcArgs){
this.mrcArgs.forEach((arg) => {
extraState.params!.push({
'name': arg.name,
'type': arg.type,
});
});
});
}
if (this.mrcImportModule) {
extraState.importModule = this.mrcImportModule;
}
Expand Down
4 changes: 2 additions & 2 deletions src/blocks/mrc_event_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ const EVENT_HANDLER = {
*/
init(this: EventHandlerBlock): void {
this.appendDummyInput('TITLE')
.appendField('When')
.appendField(Blockly.Msg.WHEN)
.appendField(createFieldNonEditableText('sender'), 'SENDER')
.appendField(createFieldNonEditableText('eventName'), 'EVENT_NAME');
this.appendDummyInput('PARAMS')
.appendField('with');
.appendField(Blockly.Msg.WITH);
this.setOutput(false);
this.setStyle(MRC_STYLE_EVENT_HANDLER);
this.appendStatementInput('STACK').appendField('');
Expand Down
4 changes: 2 additions & 2 deletions src/blocks/mrc_get_parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const GET_PARAMETER_BLOCK = {
init: function(this: GetParameterBlock): void {
this.setStyle(MRC_STYLE_VARIABLES);
this.appendDummyInput()
.appendField('parameter')
.appendField(Blockly.Msg.PARAMETER)
.appendField(createFieldNonEditableText('parameter'), 'PARAMETER_NAME');

this.setOutput(true, this.parameterType);
Expand Down Expand Up @@ -89,7 +89,7 @@ const GET_PARAMETER_BLOCK = {
}
// If we end up here it shouldn't be allowed
block.unplug(true);
blockBlock.setWarningText('Parameters can only go in their method\'s block.');
blockBlock.setWarningText(Blockly.Msg.PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK);
}
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/blocks/mrc_mechanism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const MECHANISM = {
this.setStyle(MRC_STYLE_MECHANISMS);
this.appendDummyInput()
.appendField(new Blockly.FieldTextInput('my_mech'), 'NAME')
.appendField('of type')
.appendField(Blockly.Msg.OF_TYPE)
.appendField(createFieldNonEditableText(''), 'TYPE');
this.setPreviousStatement(true, OUTPUT_NAME);
this.setNextStatement(true, OUTPUT_NAME);
Expand Down
6 changes: 3 additions & 3 deletions src/blocks/mrc_mechanism_component_holder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ const MECHANISM_COMPONENT_HOLDER = {
*/
init: function (this: MechanismComponentHolderBlock): void {
this.setInputsInline(false);
this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms');
this.appendStatementInput('COMPONENTS').setCheck(COMPONENT_OUTPUT).appendField('Components');
this.appendStatementInput('EVENTS').setCheck(EVENT_OUTPUT).appendField('Events');
this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField(Blockly.Msg.MECHANISMS);
this.appendStatementInput('COMPONENTS').setCheck(COMPONENT_OUTPUT).appendField(Blockly.Msg.COMPONENTS);
this.appendStatementInput('EVENTS').setCheck(EVENT_OUTPUT).appendField(Blockly.Msg.EVENTS);


this.setOutput(false);
Expand Down
5 changes: 2 additions & 3 deletions src/blocks/mrc_misc_evaluate_but_ignore_result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,13 @@ export const setup = function() {
Blockly.Blocks[BLOCK_NAME] = {
init: function() {
this.appendValueInput('VALUE')
.appendField('evaluate but ignore result')
.appendField(Blockly.Msg.EVALUATE_BUT_IGNORE_RESULT)
.setAlign(Blockly.inputs.Align.RIGHT);
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setStyle(MRC_STYLE_MISC);
this.setTooltip(
'Executes the connected block and ignores the result. ' +
'Allows you to call a function and ignore the return value.');
Blockly.Msg.EVALUATE_BUT_IGNORE_RESULT_TOOLTIP);
},
};
};
Expand Down
19 changes: 10 additions & 9 deletions src/blocks/mrc_opmode_details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,24 @@ const OPMODE_DETAILS = {
init: function (this: OpmodeDetailsBlock): void {
this.setStyle(MRC_STYLE_CLASS_BLOCKS);
this.appendDummyInput()
.appendField('Type')
.appendField(createFieldDropdown(['Auto', 'Teleop', 'Test']), 'TYPE')
.appendField(Blockly.Msg.TYPE)
// These aren't Blockly.Msg because they need to match the Python generator's expected values.
.appendField(createFieldDropdown(["Auto", "Teleop", "Test"]), 'TYPE')
.appendField(' ')
.appendField('Enabled')
.appendField(Blockly.Msg.ENABLED)
.appendField(new Blockly.FieldCheckbox(true), 'ENABLED');

this.appendDummyInput()
.appendField('Display Name')
.appendField(Blockly.Msg.DISPLAY_NAME)
.appendField(new Blockly.FieldTextInput(''), 'NAME')
this.appendDummyInput()
.appendField('Display Group')
.appendField(Blockly.Msg.DISPLAY_GROUP)
.appendField(new Blockly.FieldTextInput(''), 'GROUP');

this.getField('TYPE')?.setTooltip('What sort of OpMode this is');
this.getField('ENABLED')?.setTooltip('Whether the OpMode is shown on Driver Station');
this.getField('NAME')?.setTooltip('The name shown on the Driver Station. If blank will use the class name.');
this.getField('GROUP')?.setTooltip('An optional group to group OpModes on Driver Station');
this.getField('TYPE')?.setTooltip(Blockly.Msg.OPMODE_TYPE_TOOLTIP);
this.getField('ENABLED')?.setTooltip(Blockly.Msg.OPMODE_ENABLED_TOOLTIP);
this.getField('NAME')?.setTooltip(Blockly.Msg.OPMODE_NAME_TOOLTIP);
this.getField('GROUP')?.setTooltip(Blockly.Msg.OPMODE_GROUP_TOOLTIP);
},
}

Expand Down
46 changes: 46 additions & 0 deletions src/blocks/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as Blockly from 'blockly/core';
import { M } from 'vitest/dist/chunks/reporters.d.BFLkQcL6.js';

export const customTokens = (t: (key: string) => string): typeof Blockly.Msg => {
return {
ADD_COMMENT: t('BLOCKLY.ADD_COMMENT'),
REMOVE_COMMENT: t('BLOCKLY.REMOVE_COMMENT'),
DUPLICATE_COMMENT: t('BLOCKLY.DUPLICATE_COMMENT'),
OF_TYPE: t('BLOCKLY.OF_TYPE'),
WITH: t('BLOCKLY.WITH'),
WHEN: t('BLOCKLY.WHEN'),
PARAMETER: t('BLOCKLY.PARAMETER'),
PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK: t('BLOCKLY.PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK'),
MECHANISMS: t('BLOCKLY.MECHANISMS'),
COMPONENTS: t('BLOCKLY.COMPONENTS'),
EVENTS: t('BLOCKLY.EVENTS'),
EVALUATE_BUT_IGNORE_RESULT: t('BLOCKLY.EVALUATE_BUT_IGNORE_RESULT'),
EVALUATE_BUT_IGNORE_RESULT_TOOLTIP: t('BLOCKLY.EVALUATE_BUT_IGNORE_RESULT_TOOLTIP'),
AUTO: t('BLOCKLY.AUTO'),
TELEOP: t('BLOCKLY.TELEOP'),
TEST: t('BLOCKLY.TEST'),
TYPE: t('BLOCKLY.TYPE'),
ENABLED: t('BLOCKLY.ENABLED'),
DISPLAY_NAME: t('BLOCKLY.DISPLAY_NAME'),
DISPLAY_GROUP: t('BLOCKLY.DISPLAY_GROUP'),
OPMODE_TYPE_TOOLTIP: t('BLOCKLY.TOOLTIP.OPMODE_TYPE_TOOLTIP'),
OPMODE_ENABLED_TOOLTIP: t('BLOCKLY.TOOLTIP.OPMODE_ENABLED_TOOLTIP'),
OPMODE_NAME_TOOLTIP: t('BLOCKLY.TOOLTIP.OPMODE_NAME_TOOLTIP'),
OPMODE_GROUP_TOOLTIP: t('BLOCKLY.TOOLTIP.OPMODE_GROUP_TOOLTIP'),
MRC_CATEGORY_HARDWARE: t('BLOCKLY.CATEGORY.HARDWARE'),
MRC_CATEGORY_ROBOT: t('BLOCKLY.CATEGORY.ROBOT'),
MRC_CATEGORY_COMPONENTS: t('BLOCKLY.CATEGORY.COMPONENTS'),
MRC_CATEGORY_MECHANISMS: t('BLOCKLY.CATEGORY.MECHANISMS'),
MRC_CATEGORY_LOGIC: t('BLOCKLY.CATEGORY.LOGIC'),
MRC_CATEGORY_LOOPS: t('BLOCKLY.CATEGORY.LOOPS'),
MRC_CATEGORY_LISTS: t('BLOCKLY.CATEGORY.LISTS'),
MRC_CATEGORY_MATH: t('BLOCKLY.CATEGORY.MATH'),
MRC_CATEGORY_TEXT: t('BLOCKLY.CATEGORY.TEXT'),
MRC_CATEGORY_MISC: t('BLOCKLY.CATEGORY.MISC'),
MRC_CATEGORY_VARIABLES: t('BLOCKLY.CATEGORY.VARIABLES'),
MRC_CATEGORY_METHODS: t('BLOCKLY.CATEGORY.METHODS'),
MRC_CATEGORY_EVENTS: t('BLOCKLY.CATEGORY.EVENTS'),
MRC_CATEGORY_ADD_MECHANISM: t('BLOCKLY.CATEGORY.ADD_MECHANISM'),
MRC_CATEGORY_ADD_COMPONENT: t('BLOCKLY.CATEGORY.ADD_COMPONENT'),
};
};
Loading