Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blockly changes #15

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 1 addition & 3 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-async-to-generator",
"@babel/plugin-proposal-object-rest-spread",
["react-intl", {
"messagesDir": "./translations/messages/"
}]],
"react-intl"],
"presets": [
"@babel/preset-env",
"@babel/preset-react"
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"balance-text": "^3.3.1",
"base64-loader": "^1.0.0",
"bowser": "^1.9.4",
"cat-blocks": "npm:scratch-blocks@0.1.0-prerelease.20220318143026",
"cat-blocks": "workspace:scratch-blocks@*",
"classnames": "^2.2.6",
"computed-style-to-inline-style": "^3.0.0",
"cookie": "^0.6.0",
Expand Down Expand Up @@ -93,7 +93,7 @@
"redux-throttle": "^0.1.1",
"regenerator-runtime": "^0.14.1",
"scratch-audio": "^1.0.0",
"scratch-blocks": "^1.1.6",
"scratch-blocks": "workspace:*",
"scratch-l10n": "^3.18.3",
"scratch-paint": "^2.2.151",
"scratch-render": "^1.0.0",
Expand Down Expand Up @@ -129,6 +129,7 @@
"@commitlint/config-conventional": "17.8.1",
"babel-core": "7.0.0-bridge.0",
"babel-loader": "9.1.3",
"babel-plugin-react-intl": "^8.2.25",
"cross-fetch": "^4.0.0",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.8",
Expand All @@ -149,6 +150,7 @@
"redux-mock-store": "1.5.4",
"rimraf": "2.7.1",
"scratch-semantic-release-config": "1.0.14",
"scratch-blocks": "workspace:*",
"scratch-webpack-configuration": "1.3.0",
"selenium-webdriver": "3.6.0",
"semantic-release": "19.0.5",
Expand Down
3,245 changes: 1,881 additions & 1,364 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/components/monitor/monitor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const modes = {
const getCategoryColor = (theme, category) => {
const colors = getColorsForTheme(theme);
return {
background: colors[categoryColorMap[category]].primary,
background: colors[categoryColorMap[category]].colourPrimary,
text: colors.text
};
};
Expand Down
159 changes: 118 additions & 41 deletions src/containers/blocks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import DropAreaHOC from '../lib/drop-area-hoc.jsx';
import DragConstants from '../lib/drag-constants';
import defineDynamicBlock from '../lib/define-dynamic-block';
import { DEFAULT_THEME, getColorsForTheme, themeMap } from '../lib/themes';
import { injectExtensionBlockTheme, injectExtensionCategoryTheme } from '../lib/themes/blockHelpers';
import { injectExtensionBlockIcons, injectExtensionCategoryTheme, getExtensionColors } from '../lib/themes/blockHelpers';

import { connect } from 'react-redux';
import { updateToolbox } from '../reducers/toolbox';
Expand Down Expand Up @@ -92,21 +92,57 @@ class Blocks extends React.Component {
}
componentDidMount() {
this.ScratchBlocks = VMScratchBlocks(this.props.vm, this.props.useCatBlocks);
this.ScratchBlocks.prompt = this.handlePromptStart;
this.ScratchBlocks.dialog.setPrompt(this.handlePromptStart);
this.ScratchBlocks.ScratchVariables.setPromptHandler(
this.handlePromptStart
);
this.ScratchBlocks.statusButtonCallback = this.handleConnectionModalStart;
this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder;

this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker;
this.ScratchBlocks.Procedures.externalProcedureDefCallback = this.props.onActivateCustomProcedures;
this.ScratchBlocks.ScratchProcedures.externalProcedureDefCallback = this.props.onActivateCustomProcedures;
this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale);

const workspaceConfig = defaultsDeep({},
Blocks.defaultOptions,
this.props.options,
{ rtl: this.props.isRtl, toolbox: this.props.toolboxXML, colours: getColorsForTheme(this.props.theme) }
{
rtl: this.props.isRtl,
toolbox: this.props.toolboxXML,
theme: new this.ScratchBlocks.Theme(
this.props.theme,
getColorsForTheme(this.props.theme)
),
}
);
this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);

this.workspace.registerToolboxCategoryCallback(
"VARIABLE",
this.ScratchBlocks.ScratchVariables.getVariablesCategory
);

this.workspace.registerToolboxCategoryCallback('PROCEDURE',
this.ScratchBlocks.ScratchProcedures.getProceduresCategory);

this.workspace.addChangeListener((event) => {
if (
event.type === this.ScratchBlocks.Events.VAR_CREATE ||
event.type === this.ScratchBlocks.Events.VAR_RENAME ||
event.type === this.ScratchBlocks.Events.VAR_DELETE ||
(event.type === this.ScratchBlocks.Events.BLOCK_DELETE &&
event.oldJson.type === "procedures_definition") ||
// Only refresh the toolbox when procedure block creations are
// triggered by undoing a deletion (implied by recordUndo being
// false on the event).
(event.type === this.ScratchBlocks.Events.BLOCK_CREATE &&
event.json.type === "procedures_definition" &&
!event.recordUndo)
) {
this.requestToolboxUpdate();
}
});

// Register buttons under new callback keys for creating variables,
// lists, and procedures from extensions.

Expand All @@ -115,7 +151,7 @@ class Blocks extends React.Component {
const varListButtonCallback = type =>
(() => this.ScratchBlocks.Variables.createVariable(this.workspace, null, type));
const procButtonCallback = () => {
this.ScratchBlocks.Procedures.createProcedureDefCallback_(this.workspace);
this.ScratchBlocks.ScratchProcedures.createProcedureDefCallback(this.workspace);
};

const connectMicrobitRobotCallback = () => {
Expand All @@ -134,18 +170,12 @@ class Blocks extends React.Component {
// the xml can change while e.g. on the costumes tab.
this._renderedToolboxXML = this.props.toolboxXML;

// we actually never want the workspace to enable "refresh toolbox" - this basically re-renders the
// entire toolbox every time we reset the workspace. We call updateToolbox as a part of
// componentDidUpdate so the toolbox will still correctly be updated
this.setToolboxRefreshEnabled = this.workspace.setToolboxRefreshEnabled.bind(this.workspace);
this.workspace.setToolboxRefreshEnabled = () => {
this.setToolboxRefreshEnabled(false);
};

// @todo change this when blockly supports UI events
addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange);
addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange);

this.workspace.getToolbox().selectItemByPosition(0);

this.attachVM();
// Only update blocks/vm locale when visible to avoid sizing issues
// If locale changes while not visible it will get handled in didUpdate
Expand Down Expand Up @@ -174,7 +204,10 @@ class Blocks extends React.Component {
// Only rerender the toolbox when the blocks are visible and the xml is
// different from the previously rendered toolbox xml.
// Do not check against prevProps.toolboxXML because that may not have been rendered.
if (this.props.isVisible && this.props.toolboxXML !== this._renderedToolboxXML) {
if (
this.props.isVisible &&
this.props.toolboxXML !== this._renderedToolboxXML
) {
this.requestToolboxUpdate();
}

Expand All @@ -195,7 +228,6 @@ class Blocks extends React.Component {
this.setLocale();
} else {
this.props.vm.refreshWorkspace();
this.requestToolboxUpdate();
}

window.dispatchEvent(new Event('resize'));
Expand Down Expand Up @@ -223,7 +255,6 @@ class Blocks extends React.Component {
.then(() => {
this.workspace.getFlyout().setRecyclingEnabled(false);
this.props.vm.refreshWorkspace();
this.requestToolboxUpdate();
this.withToolboxUpdates(() => {
this.flyout = this.workspace.getFlyout();
this.flyout.setRecyclingEnabled(true);
Expand All @@ -247,22 +278,26 @@ class Blocks extends React.Component {
updateToolbox() {
this.toolboxUpdateTimeout = false;

const categoryId = this.workspace.toolbox_.getSelectedCategoryId();
const offset = this.workspace.toolbox_.getCategoryScrollOffset();
const scale = this.workspace.getFlyout().getWorkspace().scale;
const selectedCategoryName = this.workspace.getToolbox().getSelectedItem().getName();
const selectedCategoryScrollPosition = this.workspace.getFlyout().getCategoryScrollPosition(
selectedCategoryName).y * scale;
const offsetWithinCategory = (this.workspace.getFlyout().getWorkspace().getMetrics().viewTop
- selectedCategoryScrollPosition);
this.workspace.updateToolbox(this.props.toolboxXML);
this.workspace.getToolbox().forceRerender();
this._renderedToolboxXML = this.props.toolboxXML;

// In order to catch any changes that mutate the toolbox during "normal runtime"
// (variable changes/etc), re-enable toolbox refresh.
// Using the setter function will rerender the entire toolbox which we just rendered.
this.workspace.toolboxRefreshEnabled_ = true;

const currentCategoryPos = this.workspace.toolbox_.getCategoryPositionById(categoryId);
const currentCategoryLen = this.workspace.toolbox_.getCategoryLengthById(categoryId);
if (offset < currentCategoryLen) {
this.workspace.toolbox_.setFlyoutScrollPos(currentCategoryPos + offset);
} else {
this.workspace.toolbox_.setFlyoutScrollPos(currentCategoryPos);
const newCategoryScrollPosition = this.workspace
.getFlyout()
.getCategoryScrollPosition(selectedCategoryName);
if (newCategoryScrollPosition) {
this.workspace
.getFlyout()
.getWorkspace()
.scrollbar.setY(
newCategoryScrollPosition.y * scale + offsetWithinCategory
);
}

const queue = this.toolboxUpdateQueue;
Expand Down Expand Up @@ -351,19 +386,19 @@ class Blocks extends React.Component {
}
}
onScriptGlowOn(data) {
this.workspace.glowStack(data.id, true);
this.ScratchBlocks.glowStack(data.id, true);
}
onScriptGlowOff(data) {
this.workspace.glowStack(data.id, false);
this.ScratchBlocks.glowStack(data.id, false);
}
onBlockGlowOn(data) {
this.workspace.glowBlock(data.id, true);
// No-op, support may be added in the future
}
onBlockGlowOff(data) {
this.workspace.glowBlock(data.id, false);
// No-op, support may be added in the future
}
onVisualReport(data) {
this.workspace.reportValue(data.id, data.value);
this.ScratchBlocks.reportValue(data.id, data.value);
}
getToolboxXML() {
// Use try/catch because this requires digging pretty deep into the VM
Expand Down Expand Up @@ -404,7 +439,7 @@ class Blocks extends React.Component {

// Remove and reattach the workspace listener (but allow flyout events)
this.workspace.removeChangeListener(this.props.vm.blockListener);
const dom = this.ScratchBlocks.Xml.textToDom(data.xml);
const dom = this.ScratchBlocks.utils.xml.textToDom(data.xml);
try {
this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace);
} catch (error) {
Expand Down Expand Up @@ -464,7 +499,7 @@ class Blocks extends React.Component {
if (blockInfo.info && blockInfo.info.isDynamic) {
dynamicBlocksInfo.push(blockInfo);
} else if (blockInfo.json) {
staticBlocksJson.push(injectExtensionBlockTheme(blockInfo.json, this.props.theme));
staticBlocksJson.push(injectExtensionBlockIcons(blockInfo.json, this.props.theme));
}
// otherwise it's a non-block entry such as '---'
});
Expand All @@ -490,6 +525,40 @@ class Blocks extends React.Component {
defineBlocks(categoryInfo.menus);
defineBlocks(categoryInfo.blocks);

// Note that Blockly uses the UK spelling of "colour", so fields that
// interact directly with Blockly follow that convention, while Scratch
// code uses the US spelling of "color".
let colourPrimary = categoryInfo.color1;
let colourSecondary = categoryInfo.color2;
let colourTertiary = categoryInfo.color3;
let colourQuaternary = categoryInfo.color3;
if (this.props.theme !== DEFAULT_THEME) {
const colors = getExtensionColors(this.props.theme);
colourPrimary = colors.colourPrimary;
colourSecondary = colors.colourSecondary;
colourTertiary = colors.colourTertiary;
colourQuaternary = colors.colourQuaternary;
}
this.ScratchBlocks.getMainWorkspace()
.getTheme()
.setBlockStyle(categoryInfo.id, {
colourPrimary,
colourSecondary,
colourTertiary,
colourQuaternary,
});
this.ScratchBlocks.getMainWorkspace()
.getTheme()
.setBlockStyle(`${categoryInfo.id}_selected`, {
colourPrimary: colourQuaternary,
colourSecondary: colourQuaternary,
colourTertiary: colourQuaternary,
colourQuaternary: colourQuaternary,
});
this.ScratchBlocks.getMainWorkspace().setTheme(
this.ScratchBlocks.getMainWorkspace().getTheme()
);

// Update the toolbox with new blocks if possible
const toolboxXML = this.getToolboxXML();
if (toolboxXML) {
Expand All @@ -507,7 +576,8 @@ class Blocks extends React.Component {
}

this.withToolboxUpdates(() => {
this.workspace.toolbox_.setSelectedCategoryById(categoryId);
const toolbox = this.workspace.getToolbox();
toolbox.setSelectedItem(toolbox.getToolboxItemById(categoryId));
});
}
setBlocks(blocks) {
Expand Down Expand Up @@ -557,16 +627,15 @@ class Blocks extends React.Component {
handleCustomProceduresClose(data) {
this.props.onRequestCloseCustomProcedures(data);
const ws = this.workspace;
ws.refreshToolboxSelection_();
ws.toolbox_.scrollToCategoryById('myBlocks');
this.updateToolbox();
ws.getToolbox().selectCategoryByName('myBlocks');
}
handleDrop(dragInfo) {
fetch(dragInfo.payload.bodyUrl)
.then(response => response.json())
.then(blocks => this.props.vm.shareBlocksToTarget(blocks, this.props.vm.editingTarget.id))
.then(() => {
this.props.vm.refreshWorkspace();
this.updateToolbox(); // To show new variables/custom blocks
});
}
render() {
Expand Down Expand Up @@ -594,6 +663,7 @@ class Blocks extends React.Component {
updateMetrics: updateMetricsProp,
useCatBlocks,
workspaceMetrics,
theme,
...props
} = this.props;
/* eslint-enable no-unused-vars */
Expand Down Expand Up @@ -631,6 +701,7 @@ class Blocks extends React.Component {
media: options.media
}}
onRequestClose={this.handleCustomProceduresClose}
theme={theme}
/>
) : null}
</React.Fragment>
Expand Down Expand Up @@ -681,16 +752,22 @@ Blocks.defaultOptions = {
zoom: {
controls: true,
wheel: true,
pinch: true,
startScale: BLOCKS_DEFAULT_SCALE
},
move: {
wheel: true,
},
grid: {
spacing: 40,
length: 2,
colour: '#ddd'
},
comments: true,
collapse: false,
sounds: false
sounds: false,
trashcan: false,
modalInputs: false,
};

Blocks.defaultProps = {
Expand Down
Loading