Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
8334263
feat: add /app path and minimal blockly ui component for app usage
mashazyu Oct 14, 2025
bc00e93
feat: blockly mobile optimization
mashazyu Oct 14, 2025
40e1541
fix: error on app start
mashazyu Oct 14, 2025
bee4305
feat: move toolbox to the bottom of the screen
mashazyu Oct 14, 2025
1981b6b
feat: make search take full width
mashazyu Oct 14, 2025
fde1aac
feat: increase category height
mashazyu Oct 14, 2025
4a0b035
fix: toolbox category collapsing
mashazyu Oct 15, 2025
efc52c4
feat: update toolbox category flyout
mashazyu Oct 15, 2025
2c147b3
feat: add workplace toolbar
mashazyu Oct 15, 2025
cbc0236
fix: BlocklyApp screen height
mashazyu Oct 16, 2025
f1492c4
refactor: rename path for app and corresponding component app => embe…
mashazyu Oct 16, 2025
30f4e6f
feat: store isEmbedded flag in redux
mashazyu Oct 16, 2025
b27c16f
feat: use custom ipad styling only on /embedded route
mashazyu Oct 16, 2025
c39a7a4
refactor: remove console.logs
mashazyu Oct 16, 2025
e1cd97d
refactor: remove unnecessary compoment BlocklyApp
mashazyu Oct 16, 2025
9614d79
fix: revert css change that broke embedded view
mashazyu Oct 16, 2025
ef0af95
refactor: extract RouteHandler to separate file
mashazyu Oct 16, 2025
6509e01
feat: appy changes to BlocklyWindow only if isEmbedded true
mashazyu Oct 16, 2025
3f4b165
refactor: changes made to toolbox for embedded view
mashazyu Oct 16, 2025
a61bc68
Revert "refactor: changes made to toolbox for embedded view"
mashazyu Oct 16, 2025
e5af08d
Revert "feat: appy changes to BlocklyWindow only if isEmbedded true"
mashazyu Oct 16, 2025
7e6a865
Revert "refactor: extract RouteHandler to separate file"
mashazyu Oct 16, 2025
232da2a
refactor: extract RouteHandler to separate file take 2
mashazyu Oct 16, 2025
404da10
refactor: appy changes to BlocklyWindow only if isEmbedded true -- take2
mashazyu Oct 16, 2025
f0520d4
refactor: toolbox changes
mashazyu Oct 16, 2025
faabb0d
refactor: remove unnecessary code from EmbeddedBlockly
mashazyu Oct 16, 2025
004f3c1
fix: don't apply new styles to existing code.
mashazyu Oct 16, 2025
9dcdb38
feat: update worskpace toolbar for embedded mode
mashazyu Oct 16, 2025
fd4576f
fix: button spacing in workspace toolbar
mashazyu Oct 16, 2025
5ab43ad
feat: upate button naming
mashazyu Oct 16, 2025
03fa3bb
fix: button wording and remove user endpoint call
mashazyu Oct 16, 2025
6547752
refactor: AI suggestions
mashazyu Oct 20, 2025
ab9cf63
fix: display of blockly area on /embedded route after rfactoring
mashazyu Oct 20, 2025
3f9abea
refactor: extract reused styles
mashazyu Oct 20, 2025
c555adc
refactor: extract config from blocklywindow to config file.
mashazyu Oct 20, 2025
1621724
test: /embedded route
mashazyu Oct 20, 2025
093618e
fix: revert unnecessary styling changes
mashazyu Oct 20, 2025
9f6e632
fix: warning about undefined board
mashazyu Oct 20, 2025
e58cff3
fix: suggestion from ai to fix cy tests
mashazyu Oct 20, 2025
9c2cd9b
fix: add step to select board to start compiling
mashazyu Oct 20, 2025
d4950b7
fix: incorrect workspace toolbox button reference
mashazyu Oct 20, 2025
a64b480
fix: typing in search field & search field styling
mashazyu Oct 20, 2025
ec28f66
refactor: toolbox_styles.css
mashazyu Oct 20, 2025
f729bd5
fix: subcategory bg color
mashazyu Oct 20, 2025
7738c43
refactor: increase category height
mashazyu Oct 20, 2025
0c0e571
refactor: cleanup styles
mashazyu Oct 21, 2025
a868990
style: blockly toolbox
mashazyu Oct 21, 2025
729cd90
fix: always use tablet version of compilation on /embedded path and a…
mashazyu Oct 21, 2025
829a129
refactor: styles
mashazyu Oct 21, 2025
b0dd23f
fix: failing tests
mashazyu Oct 21, 2025
1cca951
refactor: after self-review
mashazyu Oct 21, 2025
92da900
revert: changes to Toolbox component after separate component for emb…
mashazyu Oct 21, 2025
4ab1185
revert: toolbox styling changes against development branch
mashazyu Oct 21, 2025
1cce55f
revert: minor identation, empty line changes
mashazyu Oct 21, 2025
c091c4d
refactor: unify component naming
mashazyu Oct 21, 2025
e5e42a4
refactor: make sure isEmbedded from redux state is always used
mashazyu Oct 21, 2025
e6fbfc3
style: make categories scroll in one line and always show scroll bar
mashazyu Oct 28, 2025
5eeb792
style: cleanup
mashazyu Oct 28, 2025
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
93 changes: 93 additions & 0 deletions cypress/e2e/embedded.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/// <reference types="cypress" />

describe("Embedded Blockly Page Tests", () => {
it("[Embedded] visits the embedded page", () => {
cy.visit("/embedded");
cy.url().should("include", "/embedded");
});

it("[Embedded] displays Blockly workspace", () => {
cy.visit("/embedded");
cy.get(".blocklySvg", { timeout: 10000 }).should("exist");
});

it("[Embedded] displays iPad toolbar", () => {
cy.visit("/embedded");
cy.get(".embedded-toolbar", { timeout: 10000 }).should("exist");
// Share, Reset icons exist
cy.get(".embedded-toolbar svg.fa-share-nodes").should("exist");
cy.get(".embedded-toolbar svg.fa-share").should("exist");

// Select a board so Compile button becomes available
cy.get('img[alt="Sensebox ESP"]', { timeout: 10000 }).click();
cy.get(".embedded-toolbar svg.fa-clipboard-check").should("exist");
});

it("[Embedded] displays workspace name component", () => {
cy.visit("/embedded");
cy.get(".embedded-toolbar").find("div").should("exist");
});

it("[Embedded] displays device selection", () => {
cy.visit("/embedded");
cy.get('img[alt="Sensebox ESP"]', { timeout: 10000 }).should("exist");
cy.get('img[alt="Sensebox MCU"]').should("exist");
cy.get('img[alt="Sensebox Mini"]').should("exist");
});

it("[Embedded] compiles code", () => {
cy.intercept({ method: "POST", pathname: "/compile" }).as("compile");
cy.visit("/embedded");
cy.get('img[alt="Sensebox ESP"]', { timeout: 8000 }).click();
cy.get(".embedded-toolbar svg.fa-clipboard-check").parents("button").click();
cy.wait("@compile", { responseTimeout: 30000, requestTimeout: 30000 })
.its("response.statusCode").should("eq", 200);
});

it("[Embedded] opens reset dialog", () => {
cy.visit("/embedded");
cy.get('img[alt="Sensebox ESP"]', { timeout: 8000 }).click();
cy.get(".embedded-toolbar svg.fa-share").parents("button").click();
cy.get('[role="dialog"]', { timeout: 5000 }).should("exist");
});

it("[Embedded] displays toolbox with search", () => {
cy.visit("/embedded");
cy.get(".blocklyToolbox", { timeout: 10000 }).should("exist");
cy.get('input[type="search"]').should("exist");
});

it("[Embedded] marks toolbox xml as embedded mode", () => {
cy.visit("/embedded");
cy.get('xml#blockly').should("have.class", "embedded-mode");
});

it("[Embedded] uses tablet mode for compilation with embedded-specific text", () => {
cy.intercept({ method: "POST", pathname: "/compile" }).as("compile");

cy.visit("/embedded");
cy.get('img[alt="Sensebox ESP"]', { timeout: 10000 }).click();
cy.get(".embedded-toolbar svg.fa-clipboard-check").parents("button").click();

cy.wait("@compile", { responseTimeout: 30000, requestTimeout: 30000 })
.its("response.statusCode").should("eq", 200);

cy.get('[role="dialog"]', { timeout: 10000 }).should("exist");

// Verify embedded mode specific elements
cy.get('[role="dialog"]').should("contain.text", "Gehe zum Übertragungs-Tab");
cy.get('[role="dialog"]').should("contain.text", "Over-The-Air Übertragung");
cy.get('[role="dialog"]').should("contain.text", "Der Code wurde erfolgreich kompiliert");
cy.get('[role="dialog"]').should("contain.text", "Klicke den unteren Button um zum Übertragungs-Tab zu gelangen");

// Verify stepper configuration
cy.get('[role="dialog"]').within(() => {
cy.get('.MuiStep-root').should("have.length", 2);
cy.get('.MuiStep-root').first().should("contain.text", "Kompilieren");
cy.get('.MuiStep-root').last().should("contain.text", "Übertragen");
cy.get('.MuiStepLabel-label').should("not.contain.text", "Herunterladen");
cy.get('a[href*="blocklyconnect-app://"]').should("exist");
});
});

});
20 changes: 17 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Component } from "react";

import { Router } from "react-router-dom";
import { Router, Route, Switch } from "react-router-dom";
import { createBrowserHistory } from "history";

import { Provider } from "react-redux";
Expand All @@ -17,6 +17,9 @@ import {
} from "@mui/material/styles";

import Content from "./components/Content";
import EmbeddedBlockly from "./components/EmbeddedBlockly";
import RouteHandler from "./components/RouteHandler";
import EmbeddedRoute from "./components/Route/EmbeddedRoute";
import { setCompiler } from "./actions/generalActions";

const theme = createTheme({
Expand Down Expand Up @@ -44,7 +47,10 @@ const theme = createTheme({

class App extends Component {
componentDidMount() {
store.dispatch(loadUser());
// Only call loadUser() if not on embedded route
if (window.location.pathname !== '/embedded') {
store.dispatch(loadUser());
}
// set initial compiler
store.dispatch(setCompiler(import.meta.env.VITE_INITIAL_COMPILER_URL));
}
Expand All @@ -56,8 +62,16 @@ class App extends Component {
<ThemeProvider theme={theme}>
<Provider store={store}>
<Router history={customHistory}>
<RouteHandler />
<ErrorBoundary>
<Content />
<Switch>
<EmbeddedRoute path="/embedded" exact>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: This is a route name I thought reflects the best it's purpose. But I am open for suggestions.

<EmbeddedBlockly />
</EmbeddedRoute>
<Route path="/">
<Content />
</Route>
</Switch>
</ErrorBoundary>
</Router>
</Provider>
Expand Down
8 changes: 8 additions & 0 deletions src/actions/generalActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
STATISTICS,
PLATFORM,
COMPILER,
EMBEDDED_MODE,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I don't want to create a separate copy of existing code for embedded use, rather make (minimal) changes to the existing code, if needed. This variable is to track, if user is on /embedded path.

} from "./types";

export const visitPage = () => (dispatch) => {
Expand Down Expand Up @@ -58,3 +59,10 @@ export const setCompiler = (compiler) => (dispatch) => {
payload: compiler,
});
};

export const setEmbeddedMode = (isEmbedded) => (dispatch) => {
dispatch({
type: EMBEDDED_MODE,
payload: isEmbedded,
});
};
1 change: 1 addition & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const RENDERER = "RENDERER";
export const SOUNDS = "SOUNDS";
export const STATISTICS = "STATISTICS";
export const COMPILER = "COMPILER";
export const EMBEDDED_MODE = "EMBEDDED_MODE";

// messages
export const GET_ERRORS = "GET_ERRORS";
Expand Down
41 changes: 35 additions & 6 deletions src/components/Blockly/BlocklyComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";

import * as Blockly from "blockly/core";
import "./blocks/index";
import "@/components/Blockly/generator/index";

import Toolbox from "./toolbox/Toolbox";
import EmbeddedToolbox from "./toolbox/EmbeddedToolbox";
import { reservedWords } from "./helpers/reservedWords";
import Snackbar from "../Snackbar";

Expand All @@ -26,6 +28,7 @@ export function BlocklyComponent({ initialXml, style, ...rest }) {
const blocklyDivRef = useRef(null);
const toolboxRef = useRef(null);
const [workspace, setWorkspace] = useState(undefined);
const isEmbedded = useSelector((state) => state.general.embeddedMode);

const [snackbar, setSnackbar] = useState({
open: false,
Expand All @@ -36,14 +39,24 @@ export function BlocklyComponent({ initialXml, style, ...rest }) {

// Inject Blockly once on mount
useEffect(() => {
const ws = Blockly.inject(blocklyDivRef.current, {
const blocklyOptions = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i could not find exactly where but I think the renderer for blockly got changed. Im guessing that this was not intentional. thats why the blocks look different. i think putting the renderer "Thrasos" in the blockly options makes the blocks look "normal" again.

toolbox: toolboxRef.current,
plugins: {
blockDragger: ScrollBlockDragger,
metricsManager: ScrollMetricsManager,
},
...rest,
});
};

// Only apply mobile layout options when in embedded mode
if (isEmbedded) {
blocklyOptions.horizontalLayout = true;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: This are settings to move toolbox to the bottom.

blocklyOptions.toolboxPosition = 'end';
// Ensure toolbox icon sprites and other assets load correctly in embedded view
blocklyOptions.media = '/media/blockly/';
}

const ws = Blockly.inject(blocklyDivRef.current, blocklyOptions);

setWorkspace(ws);

Expand Down Expand Up @@ -93,12 +106,28 @@ export function BlocklyComponent({ initialXml, style, ...rest }) {
ws?.dispose();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [isEmbedded]);

const cardStyle = useMemo(() => {
return isEmbedded ?{
height: "100%",
width: "100%",
} : {};
}, [isEmbedded]);

return (
<>
<Card ref={blocklyDivRef} id="blocklyDiv" style={style ? style : {}} />
<Toolbox toolbox={toolboxRef} workspace={workspace} />
<Card
ref={blocklyDivRef}
id="blocklyDiv"
style={style ? style : cardStyle}
className={isEmbedded ? "embedded-mode" : ""}
/>
{isEmbedded ? (
<EmbeddedToolbox toolbox={toolboxRef} workspace={workspace} />
) : (
<Toolbox toolbox={toolboxRef} workspace={workspace} />
)}
<Snackbar
open={snackbar.open}
message={snackbar.message}
Expand Down
63 changes: 38 additions & 25 deletions src/components/Blockly/BlocklyWindow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ZoomToFitControl } from "@blockly/zoom-to-fit";
import { Backpack } from "@blockly/workspace-backpack";
import { initialXml } from "./initialXml.js";
import { getMaxInstances } from "./helpers/maxInstances";
import { EMBEDDED_BLOCKLY_CONFIG, DEFAULT_BLOCKLY_CONFIG } from "../../config/embeddedConfig";

import BlocklySvg from "./BlocklySvg";

Expand All @@ -24,13 +25,15 @@ export default function BlocklyWindow(props) {
const sounds = useSelector((state) => state.general.sounds);
const language = useSelector((state) => state.general.language);
const selectedBoard = useSelector((state) => state.board.board);
const isEmbedded = useSelector((state) => state.general.embeddedMode);

const {
svg,
blockDisabled,
blocklyCSS,
initialXml: initialXmlProp,
zoomControls,
zoom,
grid,
move,
readOnly,
Expand Down Expand Up @@ -134,40 +137,49 @@ export default function BlocklyWindow(props) {

// Compute zoom/grid/move config with sensible defaults
const zoomConfig = useMemo(
() => ({
controls: zoomControls !== undefined ? zoomControls : true,
wheel: false,
startScale: 1,
maxScale: 3,
minScale: 0.3,
scaleSpeed: 1.2,
}),
[zoomControls],
() => {
if (zoom !== undefined) return zoom;

// Use embedded config for embedded mode, default config otherwise
const baseConfig = isEmbedded ? EMBEDDED_BLOCKLY_CONFIG.zoom : DEFAULT_BLOCKLY_CONFIG.zoom;

return {
...baseConfig,
controls: zoomControls !== undefined ? zoomControls : baseConfig.controls,
};
},
[zoom, zoomControls, isEmbedded],
);

const gridConfig = useMemo(
() =>
grid !== undefined && !grid
? {}
: {
spacing: 20,
length: 1,
colour: "#4EAF47", // senseBox-green
snap: false,
},
[grid],
() => {
if (grid === undefined || grid === false) return {};

if (typeof grid === "object") return grid;

return isEmbedded ? EMBEDDED_BLOCKLY_CONFIG.grid : DEFAULT_BLOCKLY_CONFIG.grid;
},
[grid, isEmbedded],
);

const moveConfig = useMemo(
() =>
move !== undefined && !move
? {}
: { scrollbars: true, drag: true, wheel: true },
[move],
() => {
if (move === undefined || move === false) return {};

if (typeof move === "object") return move;

return isEmbedded ? EMBEDDED_BLOCKLY_CONFIG.move : DEFAULT_BLOCKLY_CONFIG.move;
},
[move, isEmbedded],
);

const containerStyles =isEmbedded ? {
height: "100%",
width: "100%"
} : {};

return (
<div>
<div style={containerStyles}>
<BlocklyComponent
style={svg ? { height: 0 } : blocklyCSS}
readOnly={readOnly !== undefined ? readOnly : false}
Expand All @@ -193,6 +205,7 @@ BlocklyWindow.propTypes = {
blocklyCSS: PropTypes.object,
initialXml: PropTypes.string,
zoomControls: PropTypes.bool,
zoom: PropTypes.object,
grid: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
move: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
readOnly: PropTypes.bool,
Expand Down
3 changes: 3 additions & 0 deletions src/components/Blockly/msg/de/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,9 @@ export const UI = {
goToApp_text:
"Der Code wurde erfolgreich kompiliert! Klicke den unteren Button um zur senseBox:connect App zu gelangen und die Übertragung des Sketches fertigzustellen!",
goToApp_title: "Over-The-Air Übertragung",
goToApp_embedded: "Gehe zum Übertragungs-Tab",
goToApp_text_embedded:
"Der Code wurde erfolgreich kompiliert! Klicke den unteren Button um zum Übertragungs-Tab zu gelangen und die Übertragung des Sketches fertigzustellen!",

transfer_headline: "Datei übertragen",
transfer_subline:
Expand Down
3 changes: 3 additions & 0 deletions src/components/Blockly/msg/en/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,9 @@ export const UI = {
goToApp_text:
"The code has been compiled successfully. Start with the transfer by clicking on the button below, redirecting you to the senseBox:connect App ! ",
goToApp_title: "Over-The-Air Transfer",
goToApp_embedded: "Go to Transfer Tab",
goToApp_text_embedded:
"The code has been compiled successfully. Click the button below to go to the transfer tab and complete the sketch transfer!",

transfer_headline: "Transfer file",
transfer_subline: "Drag the file from your downloads folder to the senseBox",
Expand Down
Loading