Skip to content
Draft
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
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ x-app: &x-app
context: .
volumes:
- .:/app
- ../scratch-editor:/scratch-editor
- node_modules:/app/node_modules
- /var/run/docker.sock:/var/run/docker.sock
stdin_open: true
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.34.2",
"private": true,
"dependencies": {
"@RaspberryPiFoundation/scratch-gui": "0.1.0-experience-cs.20251218100358",
"@RaspberryPiFoundation/scratch-gui": "link:../scratch-editor/packages/scratch-gui",
"@apollo/client": "^3.7.8",
"@babel/core": "^7.17.10",
"@codemirror/commands": "^6.1.1",
Expand Down Expand Up @@ -170,10 +170,13 @@
"path-browserify": "^1.0.1",
"pnp-webpack-plugin": "1.6.4",
"postcss-flexbugs-fixes": "4.2.1",
"postcss-import": "12.0.1",
"postcss-loader": "3.0.0",
"postcss-normalize": "8.0.1",
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "5.0.2",
"postcss-scss": "4.0.9",
"postcss-simple-vars": "5.0.2",
"prettier": "^2.8.8",
"react-dev-utils": "^11.0.3",
"react-test-renderer": "^17.0.2",
Expand Down
91 changes: 0 additions & 91 deletions src/assets/stylesheets/ExternalStyles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,94 +5,3 @@
@use "../../../node_modules/prismjs/plugins/line-highlight/prism-line-highlight.css";
@use "../../../node_modules/material-symbols/sharp.scss";
@use "../../../node_modules/plotly.js/src/css/style.scss" as plotlyStyle;
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/oldtimey-mode/oldtimey-mode.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/modal/modal.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/account-nav.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/login-dropdown.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/settings-menu.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/user-avatar.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/menu-bar.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/author-info.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/share-button.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/save-status.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/community-button.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu-bar/project-title-input.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/nineties-mode/nineties-mode.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/blocks/blocks.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/prompt/prompt.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/icon-button/icon-button.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/question/question.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/controls/controls.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/sprite-selector/sprite-selector.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/drag-layer/drag-layer.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/connection-modal/connection-modal.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/asset-panel/selector.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/asset-panel/asset-panel.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/spinner/spinner.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/watermark/watermark.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/sprite-info/sprite-info.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/backpack/backpack.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/library/library.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/sound-editor/sound-editor.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/close-button/close-button.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/meter/meter.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/stage-wrapper/stage-wrapper.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/sprite-selector-item/sprite-selector-item.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/extension-button/extension-button.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/extension-button/extension-button.raw.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/filter/filter.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/prehistoric-mode/prehistoric-mode.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/slider-prompt/slider-prompt.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/loader/loader.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/stop-all/stop-all.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/play-button/play-button.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/green-flag/green-flag.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/menu/menu.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/target-pane/target-pane.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/library-item/library-item.raw.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/library-item/library-item.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/stage/stage.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/toggle-buttons/toggle-buttons.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/tag-button/tag-button.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/crash-message/crash-message.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/debug-modal/debug-modal.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/action-menu/action-menu.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/forms/label.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/forms/input.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/language-selector/language-selector.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/gui/gui.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/stage-header/stage-header.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/record-modal/record-modal.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/webgl-modal/webgl-modal.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/button/button.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/monitor-list/monitor-list.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/browser-modal/browser-modal.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/progress-ring/progress-ring.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/delete-confirmation-prompt/delete-confirmation-prompt.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/loupe/loupe.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/divider/divider.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/context-menu/context-menu.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/waveform/waveform.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/telemetry-modal/telemetry-modal.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/audio-trimmer/audio-trimmer.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/coming-soon/coming-soon.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/box/box.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/direction-picker/direction-picker.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/direction-picker/dial.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/mic-indicator/mic-indicator.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/turbo-mode/turbo-mode.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/alerts/alerts.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/alerts/alert.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/alerts/inline-message.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/cards/card.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/stage-selector/stage-selector.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/monitor/monitor.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/delete-button/delete-button.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/components/custom-procedures/custom-procedures.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/playground/player.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/playground/blocks-only.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/playground/index.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/css/z-index.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/css/typography.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/css/units.css";
@import "../../../node_modules/@RaspberryPiFoundation/scratch-gui/src/css/colors.css";
24 changes: 18 additions & 6 deletions src/components/Editor/Project/Project.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const WrappedGui = compose(AppStateHOC, ScratchIntegrationHOC)(GUI);

const Project = (props) => {
const webComponent = useSelector((state) => state.editor.webComponent);
const reactAppApiEndpoint = useSelector(
(state) => state.editor.reactAppApiEndpoint,
);
const [isReady, setIsReady] = useState(false);

useEffect(() => {
Expand Down Expand Up @@ -85,6 +88,20 @@ const Project = (props) => {
return <div>Loading Scratch Editor...</div>;
}

const scratchProjectHost = reactAppApiEndpoint
? `${reactAppApiEndpoint}/api/projects`
: null;
const scratchProjectId = "blank-scratch-starter";
const scratchBasePath = process.env.PUBLIC_URL || "/";
const scratchGuiProps = {
locale: "en",
menuBarHidden: true,
// assetHost: "https://editor-scratch.raspberrypi.org/api/assets",
// basePath: scratchBasePath,
// projectId: scratchProjectId,
// projectHost: scratchProjectHost,
};

return (
<div className="proj" data-testid="project">
<div
Expand Down Expand Up @@ -125,12 +142,7 @@ const Project = (props) => {
type="primary"
/>
<WrappedGui
locale="en"
menuBarHidden={true}
projectId="blank-scratch-starter"
projectHost="http://localhost:3009/api/projects"
assetHost="https://editor-scratch.raspberrypi.org/api/assets"

{...scratchGuiProps}
// assetHost="/api/assets"
// basePath="/scratch-gui/"
/>
Expand Down
102 changes: 102 additions & 0 deletions src/components/Editor/Project/ScratchIntegrationHOC.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,114 @@ const ScratchIntegrationHOC = function (WrappedComponent) {
"handleUpload",
"handleRemix",
"handleSave",
"handleBlocksChanged",
]);
}
componentDidMount() {
window.addEventListener("message", this.handleMessage);
this.props.setStageSize();
if (this.props.vm) {
console.log("Setting up VM listeners in componentDidMount...");
this.setupVMListeners();
} else {
console.log("VM not available yet in componentDidMount.");
}
}

componentDidUpdate(prevProps) {
// Set up listeners when VM becomes available
if (!prevProps.vm && this.props.vm) {
console.log("Setting up VM listeners in componentDidUpdate...");
this.setupVMListeners();
}
}

componentWillUnmount() {
window.removeEventListener("message", this.handleMessage);
this.removeVMListeners();
}

setupVMListeners() {
const vm = this.props.vm;
if (!vm) return;

console.log("=== Looking for Blockly workspace ===");

// Method 1: Check for global Blockly
if (window.Blockly) {
console.log("Found global Blockly:", window.Blockly);
const workspace = window.Blockly.getMainWorkspace?.();
console.log("Blockly main workspace:", workspace);

if (workspace) {
workspace.addChangeListener((event) => {
console.log("Blockly workspace change event:", event);
console.log("Event type:", event.type);
if (event.type === "endDrag") {
this.handleBlocksChanged();
}
});
console.log("✓ Added Blockly workspace change listener");
return; // Success!
}
}
// const vm = this.props.vm;
// if (!vm) return;

// // if (vm.runtime.getEditingTarget()) {
// // const workspace = vm.runtime.getEditingTarget().blocks;
// console.log(vm);
// console.log(vm.runtime);
// console.log(vm.runtime.constructor.PROJECT_CHANGED);
// vm.runtime.on('BLOCK_DRAG_UPDATE', this.handleBlocksChanged);
// // workspace.on('BLOCK_CREATE', this.handleBlocksChanged);
// // workspace.on('BLOCK_DELETE', this.handleBlocksChanged);
// // }
// console.log("Blocks changed listener set up...")
// // this.startPolling();
}

removeVMListeners() {
// Clean up any listeners set up in setupVMListeners
const vm = this.props.vm;
if (!vm) return;

// const workspace = vm.runtime.getEditingTarget()?.blocks;
vm.runtime.removeListener('BLOCK_DRAG_UPDATE', this.handleBlocksChanged);
}
handleBlocksChanged() {
console.log("Blocks have changed");

// Debounce to avoid saving on every tiny change
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}

this.saveTimeout = setTimeout(() => {
if (this.props.saveProjectSb3) {
this.props.saveProjectSb3().then((sb3Content) => {
console.log("Autosaving project...", sb3Content);

// Convert Blob/ArrayBuffer to base64 for localStorage
const reader = new FileReader();
reader.onloadend = () => {
const base64String = reader.result.split(',')[1]; // Remove data:application/octet-stream;base64, prefix
localStorage.setItem("autosavedProject", base64String);
console.log("Project saved to localStorage (base64)");
};
reader.readAsDataURL(sb3Content);

// This sb3Content is what you'd send to your save API
// It's the complete .sb3 file content
});
}
}, 2000); // Wait 2 seconds after last change
};

handleMessage(event) {
// These are events sent from the page telling Scratch GUI to do certain things.
// Here we are telling Scratch GUI how to do those things.
// We want this the other way around in some of these cases.
if (event.origin !== window.location.origin) return;

switch (event.data.type) {
Expand Down Expand Up @@ -103,13 +201,17 @@ const ScratchIntegrationHOC = function (WrappedComponent) {
saveProjectSb3: null,
loadProject: null,
vmReady: false,
vm: null,
};
} else {
console.log("Scratch VM is initialized");
}

return {
saveProjectSb3: vm.saveProjectSb3?.bind(vm),
loadProject: vm.loadProject?.bind(vm),
vmReady: true,
vm: vm,
};
};

Expand Down
53 changes: 7 additions & 46 deletions src/web-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ class WebComponent extends HTMLElement {
sidebarPlugins = [];

connectedCallback() {
if (!this.shadowRoot) {
this.mountPoint = this.shadowRoot;
}

console.log("Mounted web-component...");

this.mountReactApp();
Expand All @@ -43,7 +39,9 @@ class WebComponent extends HTMLElement {
if (this.root) {
console.log("Unmounted web-component...");
this.root.unmount();
this.root = null;
}
this.mountPoint = null;
store.dispatch(resetStore());
}

Expand Down Expand Up @@ -171,11 +169,13 @@ class WebComponent extends HTMLElement {
}

mountReactApp() {
if (!this.isConnected) {
return;
}
if (!this.mountPoint) {
this.mountPoint = document.createElement("div");
this.mountPoint.setAttribute("id", "root");
this.mountPoint.setAttribute("part", "editor-root");
this.attachShadow({ mode: "open" }).appendChild(this.mountPoint);
this.mountPoint.setAttribute("data-web-component-root", "editor-root");
this.appendChild(this.mountPoint);
this.root = ReactDOMClient.createRoot(this.mountPoint);
}

Expand All @@ -191,45 +191,6 @@ class WebComponent extends HTMLElement {
</Provider>
</React.StrictMode>,
);

// Copy scratch-gui styles after rendering
setTimeout(() => {
this.copyScratchGuiStyles();
}, 100); // Small delay to ensure components are rendered
}

copyScratchGuiStyles() {
const allStylesText = Array.from(document.styleSheets)
.map((sheet) => {
try {
// Only process stylesheets that contain scratch-gui related styles
// or if we can't access the href, include all stylesheets since ExternalStyles.scss contains our scratch-gui imports
const includeSheet =
!sheet.href ||
sheet.href.includes("scratch-gui") ||
sheet.href.includes("main") ||
sheet.href.includes("bundle");

if (!includeSheet) return "";

return Array.from(sheet.cssRules)
.map((rule) => rule.cssText)
.join("\n");
} catch (e) {
console.warn("Could not access stylesheet:", e);
return "";
}
})
.join("\n");

if (allStylesText && this.shadowRoot) {
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(allStylesText);
this.shadowRoot.adoptedStyleSheets = [
...(this.shadowRoot.adoptedStyleSheets || []),
styleSheet,
];
}
}
}

Expand Down
Loading
Loading