11import * as Blockly from "blockly" ;
22import { showEditorMixin } from "./fieldDropdownMixin" ;
33import { EXPORTED_VARIABLE_TYPE , IMPORTED_VARIABLE_TYPE } from "../../blocksProgram" ;
4+ import { getGlobalProgram } from "../../external" ;
45
56import svg = pxt . svgUtil ;
67
@@ -14,6 +15,8 @@ const TEXT_ARROW_PADDING = 15; // Extra padding between text end and arrow
1415 */
1516export 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