Skip to content

Commit ea19074

Browse files
committed
add custom block menu width
1 parent a99f138 commit ea19074

6 files changed

Lines changed: 200 additions & 2 deletions

File tree

src/components/blocks/blocks.css

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,36 @@
9999
border-left: 1px solid $ui-black-transparent;
100100
}
101101

102+
.flyout-resize-handle {
103+
position: absolute;
104+
top: 0;
105+
bottom: 0;
106+
width: 0.5rem;
107+
cursor: col-resize;
108+
z-index: 45;
109+
touch-action: none;
110+
}
111+
112+
.flyout-resize-handle:after {
113+
content: '';
114+
position: absolute;
115+
top: 0;
116+
bottom: 0;
117+
left: 50%;
118+
width: 1px;
119+
background: transparent;
120+
transition: background 0.12s ease;
121+
}
122+
123+
.flyout-resize-handle:hover:after {
124+
background: $ui-black-transparent;
125+
}
126+
127+
:global(body.blocks-flyout-resizing) {
128+
cursor: col-resize;
129+
user-select: none;
130+
}
131+
102132

103133
.blocks :global(.blocklyBlockDragSurface) {
104134
/*

src/components/blocks/blocks.jsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const BlocksComponent = props => {
1010
dragOver,
1111
editorBackgroundActive,
1212
editorBackgroundStyle,
13+
flyoutResizeHandleStyle,
14+
onFlyoutResizeMouseDown,
1315
style,
1416
...componentProps
1517
} = props;
@@ -22,14 +24,25 @@ const BlocksComponent = props => {
2224
{...componentProps}
2325
componentRef={containerRef}
2426
style={Object.assign({}, style, editorBackgroundStyle)}
25-
/>
27+
>
28+
{onFlyoutResizeMouseDown ? (
29+
<div
30+
className={styles.flyoutResizeHandle}
31+
style={flyoutResizeHandleStyle}
32+
onMouseDown={onFlyoutResizeMouseDown}
33+
onTouchStart={onFlyoutResizeMouseDown}
34+
/>
35+
) : null}
36+
</Box>
2637
);
2738
};
2839
BlocksComponent.propTypes = {
2940
containerRef: PropTypes.func,
3041
dragOver: PropTypes.bool,
3142
editorBackgroundActive: PropTypes.bool,
3243
editorBackgroundStyle: PropTypes.shape({}),
44+
flyoutResizeHandleStyle: PropTypes.shape({}),
45+
onFlyoutResizeMouseDown: PropTypes.func,
3346
style: PropTypes.shape({})
3447
};
3548
export default BlocksComponent;

src/containers/blocks.jsx

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ import AddonHooks from '../addons/hooks.js';
5050
import LoadScratchBlocksHOC from '../lib/tw-load-scratch-blocks-hoc.jsx';
5151
import { findTopBlock } from '../lib/backpack/code-payload.js';
5252
import { gentlyRequestPersistentStorage } from '../lib/tw-persistent-storage.js';
53+
import {
54+
getPersistentBlockFlyoutWidth,
55+
setPersistentBlockFlyoutWidth
56+
} from '../lib/tw-persistent-settings';
5357
import {
5458
EDITOR_BACKGROUND_TARGETS,
5559
getEditorBackgroundStyle,
@@ -100,6 +104,9 @@ const DroppableBlocks = DropAreaHOC([
100104
const WORKSPACE_METRICS_IDLE_MS = 500;
101105
const OFFSCREEN_CULLING_BLOCK_THRESHOLD = 1000;
102106
const 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;
103110
class 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 ? (

src/lib/tw-persistent-settings.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
const CUSTOM_UI_KEY = 'tw:customUI';
1212
const EDITOR_BACKGROUND_KEY = 'tw:editorBackground';
1313
const TOOLBOX_LAYOUT_KEY = 'tw:toolboxLayout';
14+
const BLOCK_FLYOUT_WIDTH_KEY = 'tw:blockFlyoutWidth';
1415

1516
const getLocalStorageItem = key => {
1617
try {
@@ -92,6 +93,19 @@ const setPersistentToolboxLayout = toolboxLayout => (
9293
setLocalStorageItem(TOOLBOX_LAYOUT_KEY, JSON.stringify(toolboxLayout || {}))
9394
);
9495

96+
const getPersistentBlockFlyoutWidth = (fallback = null) => {
97+
const stored = getLocalStorageItem(BLOCK_FLYOUT_WIDTH_KEY);
98+
if (stored === null) {
99+
return fallback;
100+
}
101+
const width = Number(stored);
102+
return Number.isFinite(width) ? width : fallback;
103+
};
104+
105+
const setPersistentBlockFlyoutWidth = width => (
106+
setLocalStorageItem(BLOCK_FLYOUT_WIDTH_KEY, String(width))
107+
);
108+
95109
const hydratePersistentEditorBackground = async background => {
96110
const normalized = normalizeEditorBackground(background);
97111
if (normalized.imageStorage !== EDITOR_BACKGROUND_IMAGE_STORAGE.INDEXED_DB) {
@@ -120,11 +134,14 @@ export {
120134
CUSTOM_UI_KEY,
121135
EDITOR_BACKGROUND_KEY,
122136
TOOLBOX_LAYOUT_KEY,
137+
BLOCK_FLYOUT_WIDTH_KEY,
123138
getPersistentCustomUI,
124139
setPersistentCustomUI,
125140
getPersistentEditorBackground,
126141
hydratePersistentEditorBackground,
127142
setPersistentEditorBackground,
128143
getPersistentToolboxLayout,
129-
setPersistentToolboxLayout
144+
setPersistentToolboxLayout,
145+
getPersistentBlockFlyoutWidth,
146+
setPersistentBlockFlyoutWidth
130147
};

translations/messages/node_modules/scratch-paint/src/components/mode-tools/mode-tools.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

translations/messages/node_modules/scratch-paint/src/lib/messages.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)