Skip to content

Commit 815d7b0

Browse files
authored
make local and make global toggle (#11180)
* add new global variable option to variable dropdown * can now toggle local and global * make sure the toggle updates in the dropdown and toolbox
1 parent 4e6cc11 commit 815d7b0

File tree

2 files changed

+111
-4
lines changed

2 files changed

+111
-4
lines changed

pxtblocks/builtins/variables.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ export function initVariables() {
150150
msg.DELETE_VARIABLE = lf("Delete the \"%1\" variable");
151151
msg.DELETE_VARIABLE_CONFIRMATION = lf("Delete %1 uses of the \"%2\" variable?");
152152
msg.NEW_VARIABLE_DROPDOWN = lf("New variable...");
153+
msg.NEW_GLOBAL_VARIABLE_DROPDOWN = lf("New global variable...");
154+
msg.MAKE_VARIABLE_GLOBAL = lf("Make variable \"%1\" global");
155+
msg.MAKE_VARIABLE_LOCAL = lf("Make variable \"%1\" local");
156+
msg.CANNOT_MAKE_VARIABLE_LOCAL = lf("Cannot make variable \"%1\" local because it is referenced in another file.");
153157

154158
// builtin variables_set
155159
const variablesSetId = "variables_set";

pxtblocks/plugins/newVariableField/fieldVariable.ts

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Blockly from "blockly";
22
import { showEditorMixin } from "./fieldDropdownMixin";
33
import { EXPORTED_VARIABLE_TYPE, IMPORTED_VARIABLE_TYPE } from "../../blocksProgram";
4+
import { getGlobalProgram } from "../../external";
45

56
import svg = pxt.svgUtil;
67

@@ -14,6 +15,8 @@ const TEXT_ARROW_PADDING = 15; // Extra padding between text end and arrow
1415
*/
1516
export class FieldVariable extends Blockly.FieldVariable {
1617
static CREATE_VARIABLE_ID = "CREATE_VARIABLE";
18+
static CREATE_GLOBAL_VARIABLE_ID = "CREATE_GLOBAL_VARIABLE";
19+
static TOGGLE_VARIABLE_SCOPE_ID = "TOGGLE_VARIABLE_SCOPE";
1720

1821
static dropdownCreate(this: FieldVariable): Blockly.MenuOption[] {
1922
const options = Blockly.FieldVariable.dropdownCreate.call(this) as Blockly.MenuOption[];
@@ -27,6 +30,36 @@ export class FieldVariable extends Blockly.FieldVariable {
2730
[undefined, 'SEPARATOR']
2831
);
2932

33+
options.splice(
34+
insertIndex + 1,
35+
0,
36+
[Blockly.Msg['NEW_GLOBAL_VARIABLE_DROPDOWN'], FieldVariable.CREATE_GLOBAL_VARIABLE_ID]
37+
);
38+
39+
// Add "Make variable global/local" option next to rename/delete
40+
const variable = this.getVariable();
41+
if (variable) {
42+
const varName = variable.getName();
43+
const varType = variable.getType();
44+
const isGlobal = varType === EXPORTED_VARIABLE_TYPE;
45+
const isImported = varType === IMPORTED_VARIABLE_TYPE;
46+
47+
// Only show toggle for local or exported variables (not for imported ones)
48+
if (!isImported) {
49+
const toggleLabel = isGlobal
50+
? Blockly.Msg['MAKE_VARIABLE_LOCAL'].replace('%1', varName)
51+
: Blockly.Msg['MAKE_VARIABLE_GLOBAL'].replace('%1', varName);
52+
53+
// Insert after rename, grouped with the other universal variable options
54+
const renameIndex = options.findIndex(e => e[1] === "RENAME_VARIABLE_ID");
55+
options.splice(
56+
renameIndex + 1,
57+
0,
58+
[toggleLabel, FieldVariable.TOGGLE_VARIABLE_SCOPE_ID]
59+
);
60+
}
61+
}
62+
3063
return options;
3164
}
3265

@@ -45,21 +78,89 @@ export class FieldVariable extends Blockly.FieldVariable {
4578
protected override onItemSelected_(menu: Blockly.Menu, menuItem: Blockly.MenuItem) {
4679
if (this.sourceBlock_ && !this.sourceBlock_.isDeadOrDying()) {
4780
const id = menuItem.getValue();
48-
if (id === FieldVariable.CREATE_VARIABLE_ID) {
81+
82+
// Handle variable creation (local or global)
83+
if (id === FieldVariable.CREATE_VARIABLE_ID || id === FieldVariable.CREATE_GLOBAL_VARIABLE_ID) {
84+
const variableType = id === FieldVariable.CREATE_GLOBAL_VARIABLE_ID ? EXPORTED_VARIABLE_TYPE : undefined;
85+
4986
Blockly.Variables.createVariableButtonHandler(this.sourceBlock_.workspace, name => {
50-
const newVar = this.sourceBlock_.workspace.getVariableMap().getVariable(name);
87+
const newVar = this.sourceBlock_.workspace.getVariableMap().getVariable(name, variableType);
5188

5289
if (newVar) {
5390
this.setValue(newVar.getId());
5491
}
55-
});
92+
}, variableType);
93+
return;
94+
}
95+
96+
// Handle toggling variable scope (global <-> local)
97+
if (id === FieldVariable.TOGGLE_VARIABLE_SCOPE_ID) {
98+
this.toggleVariableScope();
5699
return;
57100
}
58101
}
59102

60103
super.onItemSelected_(menu, menuItem);
61104
}
62105

106+
protected toggleVariableScope(): void {
107+
const variable = this.getVariable();
108+
if (!variable) return;
109+
110+
const workspace = this.sourceBlock_.workspace;
111+
const map = workspace.getVariableMap();
112+
const varName = variable.getName();
113+
const varId = variable.getId();
114+
const isGlobal = variable.getType() === EXPORTED_VARIABLE_TYPE;
115+
const newType = isGlobal ? '' : EXPORTED_VARIABLE_TYPE;
116+
117+
if (isGlobal) {
118+
// Check if the variable is referenced in other workspaces
119+
const program = getGlobalProgram();
120+
if (program) {
121+
const allWorkspaces = program.getAllWorkspaces();
122+
for (const fileWs of allWorkspaces) {
123+
if (fileWs.workspace === workspace) continue;
124+
125+
const importedVar = fileWs.workspace.getVariableMap().getVariableById(varId);
126+
if (importedVar) {
127+
const uses = Blockly.Variables.getVariableUsesById(fileWs.workspace, varId);
128+
if (uses.length > 0) {
129+
Blockly.dialog.alert(
130+
Blockly.Msg['CANNOT_MAKE_VARIABLE_LOCAL'].replace('%1', varName)
131+
);
132+
return;
133+
}
134+
}
135+
}
136+
}
137+
}
138+
map.changeVariableType(variable, newType);
139+
140+
// Re-render all blocks that reference this variable so the globe icon updates
141+
const uses = Blockly.Variables.getVariableUsesById(workspace, varId);
142+
for (const block of uses) {
143+
const field = block.getField("VAR");
144+
if (field) {
145+
field.forceRerender();
146+
}
147+
}
148+
149+
// Propagate changes across workspaces
150+
const program = getGlobalProgram();
151+
if (program) {
152+
program.refreshSymbols?.();
153+
}
154+
155+
// Refresh the flyout so variable blocks there reflect the updated type
156+
if (workspace instanceof Blockly.WorkspaceSvg) {
157+
const toolbox = workspace.getToolbox();
158+
if (toolbox) {
159+
(toolbox as Blockly.Toolbox).refreshSelection();
160+
}
161+
}
162+
}
163+
63164
// Everything in this class below this line is duplicated in pxtblocks/fields/field_dropown
64165
// and should be kept in sync with FieldDropdown in that file
65166
private svgRootBinding: Blockly.browserEvents.Data | null = null;
@@ -286,7 +387,9 @@ export class FieldVariable extends Blockly.FieldVariable {
286387
let iconValues: string[] = [];
287388

288389
if (varMap) {
289-
iconValues = varMap.getVariablesOfType(EXPORTED_VARIABLE_TYPE).concat(varMap.getVariablesOfType(IMPORTED_VARIABLE_TYPE)).map(v => v.getId());
390+
iconValues = varMap.getAllVariables()
391+
.filter(v => v.getType() === EXPORTED_VARIABLE_TYPE || v.getType() === IMPORTED_VARIABLE_TYPE)
392+
.map(v => v.getId());
290393
}
291394

292395
showEditorMixin.call(this, e, "icon globe", iconValues);

0 commit comments

Comments
 (0)