Skip to content
Merged
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
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,22 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d

The repo includes the Editor Web Component which shares components with the editor application but has a separate build process.

### `yarn stat:wc`
### Embedding

Runs the web component in development mode.
Open [http://localhost:3001](http://localhost:3001) to view it in the browser.
The web component can be included in a page by using the `<editor-wc>` HTML element. It takes the following attributes

There is no production build setup for the web component at present.
* `code`: A preset blob of code to show in the editor pane.
* `sense_hat_always_enabled`: Show the Astro Pi Sense HAT emulator on page load

### `yarn start:wc`

Runs the web component in development mode. Open [http://localhost:3001](http://localhost:3001) to view it in the browser.

**NB** You need to have the main `yarn start` process running too.

It is possible to add query strings to control how the web component is configured. Any HTML attribute can be set on the query string, including `class`, `style` etc.

For example, to load the page with the Sense Hat always showing, add [`?sense_hat_always_enabled` to the URL](http://localhost:3001?sense_hat_always_enabled)

## Review apps

Expand Down
31 changes: 24 additions & 7 deletions cypress/e2e/missionZero-wc.cy.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
const baseUrl = "http://localhost:3001"

beforeEach(() => {
cy.visit(baseUrl)
cy.visit(`${baseUrl}?sense_hat_always_enabled=true`)
})

it("defaults to the visual output tab", () => {
const runnerContainer = cy.get("editor-wc").shadow().find('.proj-runner-container')
runnerContainer.find('.react-tabs__tab--selected').should("contain", "Visual Output")
})

it("renders the astro pi component on page load", () => {
cy.get("editor-wc").shadow().find("#root").should("contain", "yaw")
})

it("keeps astro pi component if code run without sense hat imported", () => {
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', '')
cy.get("editor-wc").shadow().find(".btn--run").click()
cy.get("editor-wc").shadow().find("#root").should("contain", "yaw")
})

it("loads the sense hat library", () => {
Expand Down Expand Up @@ -57,15 +72,17 @@ it("confirms LEDs used when single led set", () => {
it("confirms LEDs used when display set", () => {
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'from sense_hat import SenseHat\nsense = SenseHat()\nsense.set_pixels([[100,0,0]] * 64)')
cy.get("editor-wc").shadow().find(".btn--run").click()
cy.scrollTo('bottom')
cy.get("#results").should("contain", '"usedLEDs":true')
})

// it("picks up calls to input()", () => {
// cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'input()')
// cy.get("editor-wc").shadow().find(".btn--run").click()
// cy.get("editor-wc").shadow().find("span[contenteditable=true]").type('{enter}')
// cy.get("#results").should("contain", '"noInputEvents":false')
// })
it("picks up calls to input()", () => {
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'input()')
cy.get("editor-wc").shadow().find(".btn--run").click()
cy.get("editor-wc").shadow().contains('Text Output').click()
cy.get("editor-wc").shadow().find("span[contenteditable=true]").type('{enter}')
cy.get("#results").should("contain", '"noInputEvents":false')
})

it("picks up calls to wait for motion", () => {
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'from sense_hat import SenseHat\nsense = SenseHat()\nsense.motion.wait_for_motion()')
Expand Down
26 changes: 26 additions & 0 deletions cypress/e2e/spec-wc.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,34 @@ it("renders the web component", () => {
cy.get("editor-wc").shadow().find("button").should("contain", "Run")
})

it("defaults to the text output tab", () => {
const runnerContainer = cy.get("editor-wc").shadow().find('.proj-runner-container')
runnerContainer.find('.react-tabs__tab--selected').should("contain", "Text Output")
})

it("runs the python code", () => {
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'print("Hello world")')
cy.get("editor-wc").shadow().find(".btn--run").click()
cy.get("editor-wc").shadow().find(".pythonrunner-console-output-line").should("contain", "Hello world")
})

it("does not render the astro pi component on page load", () => {
cy.get("editor-wc").shadow().contains('Visual Output').click()
cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw")
})

it("renders astro pi component if sense hat imported", () => {
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'import sense_hat')
cy.get("editor-wc").shadow().find(".btn--run").click()
cy.get("editor-wc").shadow().contains('Visual Output').click()
cy.get("editor-wc").shadow().find("#root").should("contain", "yaw")
})

it("does not render astro pi component if sense hat unimported", () => {
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'import sense_hat')
cy.get("editor-wc").shadow().find(".btn--run").click()
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', '')
cy.get("editor-wc").shadow().find(".btn--run").click()
cy.get("editor-wc").shadow().contains('Visual Output').click()
cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw")
})
5 changes: 5 additions & 0 deletions src/components/Editor/EditorSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const EditorSlice = createSlice({
codeRunStopped: false,
projectList: [],
projectListLoaded: false,
senseHatAlwaysEnabled: false,
},
reducers: {
updateImages: (state, action) => {
Expand All @@ -38,6 +39,9 @@ export const EditorSlice = createSlice({
setProjectLoaded: (state, action) => {
state.projectLoaded = action.payload;
},
setSenseHatAlwaysEnabled: (state, action) => {
state.senseHatAlwaysEnabled = action.payload;
},
triggerDraw: (state) => {
state.drawTriggered = true;
},
Expand Down Expand Up @@ -99,6 +103,7 @@ export const {
setProjectList,
setProjectListLoaded,
setProjectLoaded,
setSenseHatAlwaysEnabled,
stopCodeRun,
stopDraw,
triggerCodeRun,
Expand Down
10 changes: 5 additions & 5 deletions src/components/Editor/Runners/PythonRunner/PythonRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ const PythonRunner = () => {
const codeRunTriggered = useSelector((state) => state.editor.codeRunTriggered);
const codeRunStopped = useSelector((state) => state.editor.codeRunStopped);
const drawTriggered = useSelector((state) => state.editor.drawTriggered);
const senseHatAlwaysEnabled = useSelector((state) => state.editor.senseHatAlwaysEnabled);
const outputCanvas = useRef();
const output = useRef();
const pygalOutput = useRef();
const p5Output = useRef();
const senseHatContainer = useRef();
const dispatch = useDispatch();

const [senseHatEnabled, setSenseHatEnabled] = useState(false);
Expand Down Expand Up @@ -97,7 +97,6 @@ const PythonRunner = () => {

if (x==="./_internal_sense_hat/__init__.js") {
setSenseHatEnabled(true)
senseHatContainer.current.hidden=false
}

let localProjectFiles = projectCode.filter((component) => component.name !== 'main').map((component) => `./${component.name}.py`);
Expand Down Expand Up @@ -226,7 +225,8 @@ const PythonRunner = () => {
output.current.innerHTML = '';
pygalOutput.current.innerHTML = '';
p5Output.current.innerHTML = '';
senseHatContainer.current.hidden = true

setSenseHatEnabled(false)

var prog = projectCode[0].content;

Expand Down Expand Up @@ -299,7 +299,7 @@ const PythonRunner = () => {

return (
<div className="pythonrunner-container">
<Tabs forceRenderTabPanel={true} defaultIndex={1}>
<Tabs forceRenderTabPanel={true} defaultIndex={senseHatAlwaysEnabled ? 0 : 1}>
<TabList>
<Tab key={0}>Visual Output</Tab>
<Tab key={1}>Text Output</Tab>
Expand All @@ -312,7 +312,7 @@ const PythonRunner = () => {
<div className="pythonrunner-canvas-container">
<div id='outputCanvas' ref={outputCanvas} className="pythonrunner-graphic" />
</div>
<div id='senseHatCanvas' ref={senseHatContainer} hidden={true}>{senseHatEnabled?<AstroPiModel/>:null}</div>
<div id='senseHatCanvas'>{senseHatEnabled || senseHatAlwaysEnabled ?<AstroPiModel/>:null}</div>
</div>
</TabPanel>
<TabPanel key={1}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ exports[`Renders without crashing 1`] = `
/>
</div>
<div
hidden=""
id="senseHatCanvas"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux'
import { setProject, setProjectLoaded } from '../../Editor/EditorSlice';
import { setProject, setProjectLoaded, setSenseHatAlwaysEnabled } from '../../Editor/EditorSlice';
import WebComponentProject from '../Project/WebComponentProject';

const ProjectComponentLoader = (props) => {
const projectLoaded = useSelector((state) => state.editor.projectLoaded);
const { code } = props;
const { code, sense_hat_always_enabled } = props;
const dispatch = useDispatch()

useEffect(() => {
const proj = {
type: 'python',
components: [{ name: 'main', extension: 'py', content: code }]
}
dispatch(setSenseHatAlwaysEnabled(typeof sense_hat_always_enabled !== 'undefined'))
dispatch(setProject(proj))
dispatch(setProjectLoaded(true))
}, []);
Expand Down
11 changes: 9 additions & 2 deletions src/web-component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
<title>Editor Web component</title>
</head>
<body>
<editor-wc code=""></editor-wc>
<p id="results"></p>
</body>

<script>
document.addEventListener('DOMContentLoaded', (e) => {
const webComp = document.querySelector('editor-wc');
const webComp = document.createElement('editor-wc')
const queryParams = new URLSearchParams(window.location.search)

// Pre-set the code attribute with an empty string.
webComp.setAttribute('code', '')
// Set any attribute you like in the query string, including class, style, hidden, script, etc.
queryParams.forEach((value, key) => { webComp.setAttribute(key, value) })

// subscribe to the 'codeChanged' custom event which is pushed by the project react component
webComp.addEventListener('codeChanged', function(e) {
Expand All @@ -26,6 +31,8 @@
console.log(e.detail)
document.getElementById("results").innerText = JSON.stringify(e.detail)
})

document.getElementsByTagName('body')[0].prepend(webComp)
});
</script>
</html>
2 changes: 1 addition & 1 deletion src/web-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class WebComponent extends HTMLElement {
}

static get observedAttributes() {
return ['code'];
return ['code', 'sense_hat_always_enabled'];
}

attributeChangedCallback(name, _oldVal, newVal) {
Expand Down
Loading