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
2 changes: 2 additions & 0 deletions src/blocks/mrc_class_method_def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import * as toolboxItems from '../toolbox/items';
import { getClassData } from './utils/python';
import { FunctionData } from './utils/python_json_types';
import { findConnectedBlocksOfType } from './utils/find_connected_blocks';
import { NONCOPYABLE_BLOCK } from './noncopyable_block';
import { BLOCK_NAME as MRC_GET_PARAMETER_BLOCK_NAME } from './mrc_get_parameter';
import * as paramContainer from './mrc_param_container'

Expand Down Expand Up @@ -122,6 +123,7 @@ const CLASS_METHOD_DEF = {
this.setNextStatement(false);
this.updateBlock_();
},
...NONCOPYABLE_BLOCK,
/**
* Returns the state of this block as a JSON serializable object.
*/
Expand Down
27 changes: 23 additions & 4 deletions src/blocks/mrc_component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @license
* Copyright 2025 Porpoiseful LLC
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand Down Expand Up @@ -31,6 +31,7 @@ import { getAllowedTypesForSetCheck, getClassData, getSubclassNames } from './ut
import * as toolboxItems from '../toolbox/items';
import * as storageModule from '../storage/module';
import * as storageModuleContent from '../storage/module_content';
import { NONCOPYABLE_BLOCK } from './noncopyable_block';
import {
BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER,
MechanismComponentHolderBlock,
Expand Down Expand Up @@ -100,6 +101,7 @@ const COMPONENT = {
this.setPreviousStatement(true, OUTPUT_NAME);
this.setNextStatement(true, OUTPUT_NAME);
},
...NONCOPYABLE_BLOCK,

/**
* Returns the state of this block as a JSON serializable object.
Expand Down Expand Up @@ -192,7 +194,7 @@ const COMPONENT = {
// Collect the ports for this component block.
for (let i = 0; i < this.mrcArgs.length; i++) {
const argName = this.getArgName(i);
ports[argName] = this.mrcArgs[i].name;
ports[argName] = this.mrcArgs[i].name;
}
},
/**
Expand Down Expand Up @@ -247,6 +249,13 @@ const COMPONENT = {
this.mrcComponentId = oldIdToNewId[this.mrcComponentId];
}
},
upgrade_005_to_006: function(this: ComponentBlock) {
for (let i = 0; i < this.mrcArgs.length; i++) {
if (this.mrcArgs[i].type === 'Port') {
this.mrcArgs[i].type = this.mrcArgs[i].name;
}
}
},
};

export const setup = function () {
Expand Down Expand Up @@ -318,11 +327,21 @@ function createComponentBlock(
if (constructorData.expectedPortType) {
extraState.params!.push({
name: constructorData.expectedPortType,
type: 'Port',
type: constructorData.expectedPortType,
});
if ( moduleType == storageModule.ModuleType.ROBOT ) {
if (moduleType == storageModule.ModuleType.ROBOT ) {
inputs['ARG0'] = createPort(constructorData.expectedPortType);
}
}
return new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
}

/**
* Upgrades the ComponentBlocks in the given workspace from version 005 to 006.
* This function should only be called when upgrading old projects.
*/
export function upgrade_005_to_006(workspace: Blockly.Workspace): void {
workspace.getBlocksByType(BLOCK_NAME).forEach(block => {
(block as ComponentBlock).upgrade_005_to_006();
});
}
2 changes: 2 additions & 0 deletions src/blocks/mrc_event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
import { Parameter } from './mrc_class_method_def';
import { Editor } from '../editor/editor';
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import { NONCOPYABLE_BLOCK } from './noncopyable_block';
import * as paramContainer from './mrc_param_container'
import {
BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER,
Expand Down Expand Up @@ -80,6 +81,7 @@ const EVENT = {
this.setNextStatement(true, OUTPUT_NAME);
this.updateBlock_();
},
...NONCOPYABLE_BLOCK,

/**
* Returns the state of this block as a JSON serializable object.
Expand Down
22 changes: 14 additions & 8 deletions src/blocks/mrc_mechanism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import * as toolboxItems from '../toolbox/items';
import * as storageModule from '../storage/module';
import * as storageModuleContent from '../storage/module_content';
import * as storageNames from '../storage/names';
import { NONCOPYABLE_BLOCK } from './noncopyable_block';
import {
BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER,
MechanismComponentHolderBlock,
Expand All @@ -48,6 +49,8 @@ export const FIELD_TYPE = 'TYPE';
type Parameter = {
name: string,
type: string,
componentId?: string,
componentPortsIndex?: number, // The zero-based number when iterating through component.ports.
};

type MechanismExtraState = {
Expand Down Expand Up @@ -94,6 +97,7 @@ const MECHANISM = {
this.setPreviousStatement(true, OUTPUT_NAME);
this.setNextStatement(true, OUTPUT_NAME);
},
...NONCOPYABLE_BLOCK,

/**
* Returns the state of this block as a JSON serializable object.
Expand All @@ -105,10 +109,7 @@ const MECHANISM = {
};
extraState.parameters = [];
this.mrcParameters.forEach((arg) => {
extraState.parameters!.push({
name: arg.name,
type: arg.type,
});
extraState.parameters!.push({...arg});
});
if (this.mrcImportModule) {
extraState.importModule = this.mrcImportModule;
Expand All @@ -125,10 +126,7 @@ const MECHANISM = {
this.mrcParameters = [];
if (extraState.parameters) {
extraState.parameters.forEach((arg) => {
this.mrcParameters.push({
name: arg.name,
type: arg.type,
});
this.mrcParameters.push({...arg});
});
}
this.updateBlock_();
Expand Down Expand Up @@ -289,11 +287,15 @@ const MECHANISM = {
}
this.mrcParameters = [];
components.forEach(component => {
let componentPortsIndex = 0;
for (const port in component.ports) {
this.mrcParameters.push({
name: port,
type: component.ports[port],
componentId: component.componentId,
componentPortsIndex,
});
componentPortsIndex++;
}
});
this.updateBlock_();
Expand Down Expand Up @@ -363,13 +365,17 @@ export function createMechanismBlock(
const inputs: {[key: string]: any} = {};
let i = 0;
components.forEach(component => {
let componentPortsIndex = 0;
for (const port in component.ports) {
const parameterType = component.ports[port];
extraState.parameters?.push({
name: port,
type: parameterType,
componentId: component.componentId,
componentPortsIndex,
});
inputs['ARG' + i] = createPort(parameterType);
componentPortsIndex++;
i++;
}
});
Expand Down
2 changes: 2 additions & 0 deletions src/blocks/mrc_mechanism_component_holder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Editor } from '../editor/editor';
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import * as storageModule from '../storage/module';
import * as storageModuleContent from '../storage/module_content';
import { NONCOPYABLE_BLOCK } from './noncopyable_block';
import { BLOCK_NAME as MRC_MECHANISM_NAME } from './mrc_mechanism';
import { OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism';
import { MechanismBlock } from './mrc_mechanism';
Expand Down Expand Up @@ -79,6 +80,7 @@ const MECHANISM_COMPONENT_HOLDER = {
this.mrcEventBlockIds = '';
this.mrcToolboxUpdateTimeout = null;
},
...NONCOPYABLE_BLOCK,
saveExtraState: function (this: MechanismComponentHolderBlock): MechanismComponentHolderExtraState {
const extraState: MechanismComponentHolderExtraState = {
};
Expand Down
2 changes: 2 additions & 0 deletions src/blocks/mrc_opmode_details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Editor } from '../editor/editor';
import { ExtendedPythonGenerator, OpModeDetails } from '../editor/extended_python_generator';
import { createFieldDropdown } from '../fields/FieldDropdown';
import { MRC_STYLE_CLASS_BLOCKS } from '../themes/styles';
import { NONCOPYABLE_BLOCK } from './noncopyable_block';

export const BLOCK_NAME = 'mrc_opmode_details';

Expand Down Expand Up @@ -65,6 +66,7 @@ const OPMODE_DETAILS = {
this.getField('NAME')?.setTooltip(Blockly.Msg.OPMODE_NAME_TOOLTIP);
this.getField('GROUP')?.setTooltip(Blockly.Msg.OPMODE_GROUP_TOOLTIP);
},
...NONCOPYABLE_BLOCK,
checkOpMode(this: OpmodeDetailsBlock, editor: Editor): void {
if (editor.isStepsInWorkspace() ||
editor.getMethodNamesAlreadyOverriddenInWorkspace().includes(PERIODIC_METHOD_NAME)) {
Expand Down
4 changes: 3 additions & 1 deletion src/blocks/mrc_port.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @license
* Copyright 2025 Porpoiseful LLC
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand All @@ -26,6 +26,7 @@ import { MRC_STYLE_PORTS } from '../themes/styles'
import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import { createFieldNumberDropdown } from '../fields/field_number_dropdown';
import { getOutputCheck } from './utils/python';

export const BLOCK_NAME = 'mrc_port';
export const OUTPUT_NAME = 'mrc_port';
Expand Down Expand Up @@ -119,6 +120,7 @@ const PORT = {
}
this.mrcPortType = state.portType;
this.mrcPortCount = iField;
this.setOutput(true, getOutputCheck(this.mrcPortType));
},
}

Expand Down
4 changes: 3 additions & 1 deletion src/blocks/mrc_steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

/**
* @fileoverview Blocks for class method definition
* @fileoverview Block for defining steps.
* @author [email protected] (Alan Smith)
*/
import * as Blockly from 'blockly';
Expand All @@ -25,6 +25,7 @@ import { Order } from 'blockly/python';
import { MRC_STYLE_STEPS } from '../themes/styles';
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import { createStepFieldFlydown } from '../fields/field_flydown';
import { NONCOPYABLE_BLOCK } from './noncopyable_block';
import { renameSteps as updateJumpToStepBlocks } from './mrc_jump_to_step';
import * as stepContainer from './mrc_step_container'
import { createBooleanShadowValue } from './utils/value';
Expand Down Expand Up @@ -63,6 +64,7 @@ const STEPS = {
this.setStyle(MRC_STYLE_STEPS);
this.setMutator(stepContainer.getMutatorIcon(this));
},
...NONCOPYABLE_BLOCK,
saveExtraState: function (this: StepsBlock): StepsExtraState {
return {
stepNames: this.mrcStepNames,
Expand Down
36 changes: 36 additions & 0 deletions src/blocks/noncopyable_block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @license
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @author [email protected] (Liz Looney)
*/

import * as Blockly from 'blockly';

export const NONCOPYABLE_BLOCK = {
isCopyable: function(this: Blockly.BlockSvg): boolean {
// We don't allow copying, but we return true here so that toCopyData will
// be called. The default in blockly is that a block is only copyable if it
// is both deletable and movable.
return true;
},
toCopyData: function(this: Blockly.BlockSvg, _addNextBlocks = false): Blockly.clipboard.BlockCopyData | null {
// We don't allow copying, but we return null here so the previous contents
// of the clipboard is cleared.
return null;
},
}
27 changes: 8 additions & 19 deletions src/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,19 +301,6 @@ export class Editor {
}
}

public isModified(): boolean {
/*
// This code is helpful for debugging issues where the editor says
// 'Blocks have been modified!'.
if (this.getModuleContentText() !== this.moduleContentText) {
console.log('isModified will return true');
console.log('this.getModuleContentText() is ' + this.getModuleContentText());
console.log('this.moduleContentText is ' + this.moduleContentText);
}
*/
return this.getModuleContentText() !== this.moduleContentText;
}

public getBlocklyWorkspace(): Blockly.WorkspaceSvg {
return this.blocklyWorkspace;
}
Expand Down Expand Up @@ -470,12 +457,14 @@ export class Editor {

public async saveModule(): Promise<string> {
const moduleContentText = this.getModuleContentText();
try {
await this.storage.saveFile(this.modulePath, moduleContentText);
this.moduleContentText = moduleContentText;
await storageProject.saveProjectInfo(this.storage, this.projectName);
} catch (e) {
throw e;
if (moduleContentText !== this.moduleContentText) {
try {
await this.storage.saveFile(this.modulePath, moduleContentText);
this.moduleContentText = moduleContentText;
await storageProject.saveProjectInfo(this.storage, this.projectName);
} catch (e) {
throw e;
}
}
return moduleContentText;
}
Expand Down
15 changes: 8 additions & 7 deletions src/reactComponents/TabContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({
setAlertErrorMessage,
isActive,
}, ref) => {
const [blocklyComponent, setBlocklyComponent] = React.useState<BlocklyComponentType | null>(null);
const blocklyComponent = React.useRef<BlocklyComponentType | null>(null);
const [editorInstance, setEditorInstance] = React.useState<editor.Editor | null>(null);
const [generatedCode, setGeneratedCode] = React.useState<string>('');
const [triggerPythonRegeneration, setTriggerPythonRegeneration] = React.useState(0);
Expand Down Expand Up @@ -116,15 +116,16 @@ export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({
}

// Check if this event is for our workspace
if (blocklyComponent && event.workspaceId === blocklyComponent.getBlocklyWorkspace().id) {
if (blocklyComponent.current &&
event.workspaceId === blocklyComponent.current.getBlocklyWorkspace().id) {
setTriggerPythonRegeneration(Date.now());
// Also notify parent
}
}, [blocklyComponent]);

/** Called when BlocklyComponent is created. */
const setupBlocklyComponent = React.useCallback((_modulePath: string, newBlocklyComponent: BlocklyComponentType) => {
setBlocklyComponent(newBlocklyComponent);
blocklyComponent.current = newBlocklyComponent;
newBlocklyComponent.setActive(isActive);
}, [isActive]);

Expand Down Expand Up @@ -156,8 +157,8 @@ export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({

/** Update active state when isActive changes. */
React.useEffect(() => {
if (blocklyComponent) {
blocklyComponent.setActive(isActive);
if (blocklyComponent.current) {
blocklyComponent.current.setActive(isActive);
}
if (editorInstance && isActive) {
editorInstance.makeCurrent(project, modulePathToContentText);
Expand All @@ -166,9 +167,9 @@ export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({

/** Generate code when regeneration is triggered. */
React.useEffect(() => {
if (blocklyComponent && module) {
if (blocklyComponent.current && module) {
const code = extendedPythonGenerator.mrcWorkspaceToCode(
blocklyComponent.getBlocklyWorkspace(),
blocklyComponent.current.getBlocklyWorkspace(),
module
);
setGeneratedCode(code);
Expand Down
Loading