Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 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
cc9d978
feat: add text to workspace buttons
mashazyu Nov 12, 2025
69b1801
feat: wrap blockly categories
mashazyu Nov 12, 2025
f3cf902
fix: wrapping of workspace buttons on embedded path
mashazyu Dec 10, 2025
84ef73b
feat: zoom out more by default on embedded path
mashazyu Dec 11, 2025
f572fd2
feat: decrease more start scale
mashazyu Dec 18, 2025
f9f853e
feat: allow closing Comp
mashazyu Dec 18, 2025
b56aae8
feat: limit toolbox subcategory height
mashazyu Dec 18, 2025
c020d2a
feat: increase trash can area in blockly
mashazyu Dec 18, 2025
6270d9f
fix: toolbox button margins
mashazyu Dec 18, 2025
1c596d7
fix: toolbox styling
mashazyu Dec 18, 2025
17a8b37
fix: onChangeWorkspace not firing
Thiemann96 Jan 9, 2026
c8b397b
Log code after dispatching workspace change
Thiemann96 Jan 9, 2026
be3bf1f
fix: updated code is sent to compiler
mashazyu Jan 12, 2026
e859ee2
fix: type warning in case compilation ended with error
mashazyu Jan 12, 2026
38acc32
revert: workspace changes and logging
mashazyu Jan 12, 2026
f0fd458
feat: on embedded path close transfer modal after user is transferre…
mashazyu Jan 14, 2026
69cbdb0
Merge branch 'development' into feat/add-app-blockly-version
Thiemann96 Jan 14, 2026
96e8feb
fix: update routing to v6
Thiemann96 Jan 14, 2026
9f063dc
feat: collapse categories when item is dragged to work area
mashazyu Jan 14, 2026
4853520
add: setting embed mode as hook is used
Thiemann96 Jan 14, 2026
64ebaa9
fix: toolbox layout on /embedded path
mashazyu Jan 14, 2026
32c1f37
test: comment out search box test as it is not supported atm
mashazyu Jan 14, 2026
6482e07
fix: /embedded path recognition after rebase
mashazyu Jan 14, 2026
4009a2a
refactor: remove extra setting of embedded mode
mashazyu Jan 14, 2026
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
94 changes: 94 additions & 0 deletions cypress/e2e/embedded.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/// <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");
});

// Search box is currently disabled in embedded mode
// 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");
});
});

});
10 changes: 8 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { Component } from "react";
import { BrowserRouter } from "react-router-dom";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./store";
import { loadUser } from "./actions/authActions";
Expand All @@ -12,6 +12,8 @@ import {
} from "@mui/material/styles";

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

const theme = createTheme({
Expand Down Expand Up @@ -61,8 +63,12 @@ class App extends Component {
<ThemeProvider theme={theme}>
<Provider store={store}>
<BrowserRouter>
<RouteHandler />
<ErrorBoundary>
<Content />
<Routes>
<Route path="/embedded" element={<EmbeddedBlockly />} />
<Route path="/*" element={<Content />} />
</Routes>
</ErrorBoundary>
</BrowserRouter>
</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 @@ -58,6 +58,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
1 change: 1 addition & 0 deletions src/actions/workspaceActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const onChangeCode = () => (dispatch, getState) => {
export const onChangeWorkspace = (event) => (dispatch, getState) => {
dispatch(workspaceChange());
var code = dispatch(onChangeCode());

dispatch(storeTutorialXml(code.xml));
var stats = getState().workspace.stats;
if (event.type === Blockly.Events.BLOCK_CREATE) {
Expand Down
78 changes: 72 additions & 6 deletions src/components/Blockly/BlocklyComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
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 { useLocation } from "react-router-dom";

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";
import { EMBEDDED_CONFIG } from "../../config/embeddedConfig";

import "blockly/blocks";
import {
Expand All @@ -25,6 +29,10 @@ export function BlocklyComponent({ initialXml, style, ...rest }) {
const blocklyDivRef = useRef(null);
const toolboxRef = useRef(null);
const [workspace, setWorkspace] = useState(undefined);
const location = useLocation();
const isEmbeddedRedux = useSelector((state) => state.general.embeddedMode);
const isEmbedded = isEmbeddedRedux || location.pathname === EMBEDDED_CONFIG.ROUTE;

const [snackbar, setSnackbar] = useState({
open: false,
message: "",
Expand All @@ -34,14 +42,56 @@ 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
// These must override any options from ...rest, so set them after
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
if (!blocklyOptions.media) {
blocklyOptions.media = '/media/blockly/';
}
}

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

if (isEmbedded && ws.trashcan) {
const originalGetClientRect = ws.trashcan.getClientRect.bind(ws.trashcan);
const originalGetBoundingRectangle = ws.trashcan.getBoundingRectangle.bind(ws.trashcan);

ws.trashcan.getClientRect = function() {
const originalRect = originalGetClientRect();
if (!originalRect) return null;

return new Blockly.utils.Rect(
originalRect.top - 80,
originalRect.bottom + 80,
originalRect.left - 80,
originalRect.right + 80
);
};

ws.trashcan.getBoundingRectangle = function() {
const originalRect = originalGetBoundingRectangle();
if (!originalRect) return null;

return new Blockly.utils.Rect(
originalRect.top - 80,
originalRect.bottom + 80,
originalRect.left - 80,
originalRect.right + 80
);
};
}

setWorkspace(ws);

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

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
65 changes: 40 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,12 +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 @@ -154,39 +158,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
style={
tutorial
Expand All @@ -197,7 +211,7 @@ export default function BlocklyWindow(props) {
width: "100%",
height: "100%",
}
: {}
: {containerStyles}
}
>
<BlocklyComponent
Expand Down Expand Up @@ -225,6 +239,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
Loading