@@ -50,6 +50,10 @@ import AddonHooks from '../addons/hooks.js';
5050import LoadScratchBlocksHOC from '../lib/tw-load-scratch-blocks-hoc.jsx' ;
5151import { findTopBlock } from '../lib/backpack/code-payload.js' ;
5252import { gentlyRequestPersistentStorage } from '../lib/tw-persistent-storage.js' ;
53+ import {
54+ getPersistentBlockFlyoutWidth ,
55+ setPersistentBlockFlyoutWidth
56+ } from '../lib/tw-persistent-settings' ;
5357import {
5458 EDITOR_BACKGROUND_TARGETS ,
5559 getEditorBackgroundStyle ,
@@ -100,6 +104,9 @@ const DroppableBlocks = DropAreaHOC([
100104const WORKSPACE_METRICS_IDLE_MS = 500 ;
101105const OFFSCREEN_CULLING_BLOCK_THRESHOLD = 1000 ;
102106const BLOCK_DRAG_PORTAL_Z_INDEX = 1000 ;
107+ const DEFAULT_FLYOUT_WIDTH = 250 ;
108+ const DEFAULT_FLYOUT_CATEGORY_WIDTH = 60 ;
109+ const MIN_FLYOUT_WIDTH = 180 ;
103110class Blocks extends React . Component {
104111 constructor ( props ) {
105112 super ( props ) ;
@@ -144,6 +151,11 @@ class Blocks extends React.Component {
144151 'flushWorkspaceChromeRefresh' ,
145152 'scheduleProcedureReturnsToolboxRefresh' ,
146153 'flushProcedureReturnsToolboxRefresh' ,
154+ 'patchFlyoutWidthSupport' ,
155+ 'applyFlyoutWidth' ,
156+ 'startFlyoutResize' ,
157+ 'handleFlyoutResizeMove' ,
158+ 'stopFlyoutResize' ,
147159 'requestToolboxStateSync' ,
148160 'flushToolboxStateSync' ,
149161 'syncWorkspaceCullingState' ,
@@ -189,12 +201,16 @@ class Blocks extends React.Component {
189201 this . isLargeWorkspace = false ;
190202 this . blockDragPortal = null ;
191203 this . restoreBlockDropTargetOutsideCheck = null ;
204+ this . flyoutWidth = this . clampFlyoutWidth ( getPersistentBlockFlyoutWidth ( DEFAULT_FLYOUT_WIDTH ) ) ;
205+ this . flyoutCategoryWidth = DEFAULT_FLYOUT_CATEGORY_WIDTH ;
206+ this . flyoutResizeStart = null ;
192207 }
193208 componentDidMount ( ) {
194209 this . ScratchBlocks = VMScratchBlocks ( this . props . vm , this . props . useCatBlocks ) ;
195210 this . ScratchBlocks . prompt = this . handlePromptStart ;
196211 this . ScratchBlocks . statusButtonCallback = this . handleConnectionModalStart ;
197212 this . ScratchBlocks . recordSoundCallback = this . handleOpenSoundRecorder ;
213+ this . patchFlyoutWidthSupport ( ) ;
198214
199215 this . ScratchBlocks . FieldColourSlider . activateEyedropper_ = this . props . onActivateColorPicker ;
200216 this . ScratchBlocks . Procedures . externalProcedureDefCallback = this . props . onActivateCustomProcedures ;
@@ -222,6 +238,7 @@ class Blocks extends React.Component {
222238 ) ;
223239 this . workspace = this . ScratchBlocks . inject ( this . blocks , workspaceConfig ) ;
224240 AddonHooks . blocklyWorkspace = this . workspace ;
241+ this . applyFlyoutWidth ( this . flyoutWidth , false ) ;
225242 this . installBlockDropTargetOutsideCheck ( ) ;
226243 this . installBlockDragPortal ( ) ;
227244 this . syncWorkspaceCullingState ( ) ;
@@ -330,6 +347,10 @@ class Blocks extends React.Component {
330347 this . requestToolboxUpdate ( ) ;
331348 }
332349
350+ if ( this . workspace && this . props . isVisible && this . flyoutWidth !== ( this . workspace . getFlyout && this . workspace . getFlyout ( ) ? this . workspace . getFlyout ( ) . getWidth ( ) : this . flyoutWidth ) ) {
351+ this . applyFlyoutWidth ( this . flyoutWidth , false ) ;
352+ }
353+
333354 if ( this . pendingProcedureReturnsToolboxRefresh && this . props . isVisible ) {
334355 this . scheduleProcedureReturnsToolboxRefresh ( ) ;
335356 }
@@ -417,6 +438,7 @@ class Blocks extends React.Component {
417438 cancelAnimationFrame ( this . procedureReturnsRefreshFrame ) ;
418439 this . procedureReturnsRefreshFrame = null ;
419440 }
441+ this . stopFlyoutResize ( ) ;
420442 if ( window . __twEnableProcedureReturns ) {
421443 delete window . __twEnableProcedureReturns ;
422444 }
@@ -698,6 +720,105 @@ class Blocks extends React.Component {
698720 this . pendingProcedureReturnsToolboxXML = null ;
699721 }
700722 }
723+ clampFlyoutWidth ( width ) {
724+ return Math . max ( MIN_FLYOUT_WIDTH , width ) ;
725+ }
726+ patchFlyoutWidthSupport ( ) {
727+ const ScratchBlocks = this . ScratchBlocks ;
728+ if ( ! ScratchBlocks || ! ScratchBlocks . Flyout || ! ScratchBlocks . Toolbox ) {
729+ return ;
730+ }
731+ if ( ! ScratchBlocks . Flyout . prototype . setWidth ) {
732+ const originalGetWidth = ScratchBlocks . Flyout . prototype . getWidth ;
733+ ScratchBlocks . Flyout . prototype . getWidth = function ( ) {
734+ return typeof this . customWidth_ === 'number' ? this . customWidth_ : originalGetWidth . call ( this ) ;
735+ } ;
736+ ScratchBlocks . Flyout . prototype . setWidth = function ( width ) {
737+ this . customWidth_ = width ;
738+ } ;
739+ }
740+ if ( ! ScratchBlocks . Toolbox . prototype . setWidth ) {
741+ ScratchBlocks . Toolbox . prototype . setWidth = function ( width ) {
742+ this . width = width ;
743+ } ;
744+ }
745+ }
746+ getFlyoutResizeClientX ( event ) {
747+ if ( event . touches && event . touches . length ) {
748+ return event . touches [ 0 ] . clientX ;
749+ }
750+ if ( event . changedTouches && event . changedTouches . length ) {
751+ return event . changedTouches [ 0 ] . clientX ;
752+ }
753+ return event . clientX ;
754+ }
755+ applyFlyoutWidth ( width , persist = true ) {
756+ if ( ! this . workspace ) {
757+ return ;
758+ }
759+ const toolbox = this . workspace . getToolbox ? this . workspace . getToolbox ( ) : this . workspace . toolbox_ ;
760+ const flyout = this . workspace . getFlyout ? this . workspace . getFlyout ( ) : this . workspace . flyout_ ;
761+ if ( ! toolbox || ! flyout || typeof flyout . setWidth !== 'function' || typeof toolbox . setWidth !== 'function' ) {
762+ return ;
763+ }
764+
765+ const nextWidth = this . clampFlyoutWidth ( width ) ;
766+ if ( this . flyoutCategoryWidth === DEFAULT_FLYOUT_CATEGORY_WIDTH ) {
767+ this . flyoutCategoryWidth = toolbox . getWidth ( ) - flyout . getWidth ( ) ;
768+ }
769+
770+ flyout . setWidth ( nextWidth ) ;
771+ toolbox . setWidth ( this . flyoutCategoryWidth + nextWidth ) ;
772+ this . flyoutWidth = nextWidth ;
773+
774+ if ( persist ) {
775+ setPersistentBlockFlyoutWidth ( nextWidth ) ;
776+ }
777+
778+ this . forceUpdate ( ) ;
779+ this . scheduleWorkspaceChromeRefresh ( ) ;
780+ }
781+ startFlyoutResize ( event ) {
782+ if ( event . button !== undefined && event . button !== 0 ) {
783+ return ;
784+ }
785+ event . preventDefault ( ) ;
786+ event . stopPropagation ( ) ;
787+
788+ this . flyoutResizeStart = {
789+ clientX : this . getFlyoutResizeClientX ( event ) ,
790+ width : this . flyoutWidth
791+ } ;
792+ document . body . classList . add ( 'blocks-flyout-resizing' ) ;
793+ document . addEventListener ( 'mousemove' , this . handleFlyoutResizeMove ) ;
794+ document . addEventListener ( 'mouseup' , this . stopFlyoutResize ) ;
795+ document . addEventListener ( 'touchmove' , this . handleFlyoutResizeMove , { passive : false } ) ;
796+ document . addEventListener ( 'touchend' , this . stopFlyoutResize ) ;
797+ document . addEventListener ( 'touchcancel' , this . stopFlyoutResize ) ;
798+ }
799+ handleFlyoutResizeMove ( event ) {
800+ if ( ! this . flyoutResizeStart ) {
801+ return ;
802+ }
803+ event . preventDefault ( ) ;
804+ const clientX = this . getFlyoutResizeClientX ( event ) ;
805+ const delta = this . props . isRtl ?
806+ this . flyoutResizeStart . clientX - clientX :
807+ clientX - this . flyoutResizeStart . clientX ;
808+ this . applyFlyoutWidth ( this . flyoutResizeStart . width + delta ) ;
809+ }
810+ stopFlyoutResize ( ) {
811+ if ( ! this . flyoutResizeStart ) {
812+ return ;
813+ }
814+ this . flyoutResizeStart = null ;
815+ document . body . classList . remove ( 'blocks-flyout-resizing' ) ;
816+ document . removeEventListener ( 'mousemove' , this . handleFlyoutResizeMove ) ;
817+ document . removeEventListener ( 'mouseup' , this . stopFlyoutResize ) ;
818+ document . removeEventListener ( 'touchmove' , this . handleFlyoutResizeMove ) ;
819+ document . removeEventListener ( 'touchend' , this . stopFlyoutResize ) ;
820+ document . removeEventListener ( 'touchcancel' , this . stopFlyoutResize ) ;
821+ }
701822 syncWorkspaceCullingState ( ) {
702823 if ( ! this . workspace || ! this . workspace . setOffscreenTopBlockCullingEnabled ) {
703824 return ;
@@ -1566,13 +1687,20 @@ class Blocks extends React.Component {
15661687 editorBackground ,
15671688 EDITOR_BACKGROUND_TARGETS . BLOCKS
15681689 ) ;
1690+ const flyoutResizeHandleStyle = this . props . isRtl ? {
1691+ right : this . flyoutCategoryWidth + this . flyoutWidth - 4
1692+ } : {
1693+ left : this . flyoutCategoryWidth + this . flyoutWidth - 4
1694+ } ;
15691695 return (
15701696 < React . Fragment >
15711697 < DroppableBlocks
15721698 componentRef = { this . setBlocks }
15731699 editorBackgroundActive = { editorBackgroundActive }
15741700 editorBackgroundStyle = { editorBackgroundActive ? getEditorBackgroundStyle ( editorBackground ) : null }
1701+ flyoutResizeHandleStyle = { flyoutResizeHandleStyle }
15751702 onDrop = { this . handleDrop }
1703+ onFlyoutResizeMouseDown = { this . startFlyoutResize }
15761704 { ...props }
15771705 />
15781706 { this . state . prompt ? (
0 commit comments