Skip to content

Commit f685fa9

Browse files
give the web component the option to enable the senseHAT on page load (#173)
Adds a `sense_hat_always_enabled` attribute to the web component that ensures the sense hat is always shown on page load.
1 parent 7a42015 commit f685fa9

9 files changed

Lines changed: 87 additions & 22 deletions

File tree

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,22 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d
4848

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

51-
### `yarn stat:wc`
51+
### Embedding
5252

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

56-
There is no production build setup for the web component at present.
55+
* `code`: A preset blob of code to show in the editor pane.
56+
* `sense_hat_always_enabled`: Show the Astro Pi Sense HAT emulator on page load
57+
58+
### `yarn start:wc`
59+
60+
Runs the web component in development mode. Open [http://localhost:3001](http://localhost:3001) to view it in the browser.
61+
62+
**NB** You need to have the main `yarn start` process running too.
63+
64+
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.
65+
66+
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)
5767

5868
## Review apps
5969

cypress/e2e/missionZero-wc.cy.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
const baseUrl = "http://localhost:3001"
22

33
beforeEach(() => {
4-
cy.visit(baseUrl)
4+
cy.visit(`${baseUrl}?sense_hat_always_enabled=true`)
5+
})
6+
7+
it("defaults to the visual output tab", () => {
8+
const runnerContainer = cy.get("editor-wc").shadow().find('.proj-runner-container')
9+
runnerContainer.find('.react-tabs__tab--selected').should("contain", "Visual Output")
10+
})
11+
12+
it("renders the astro pi component on page load", () => {
13+
cy.get("editor-wc").shadow().find("#root").should("contain", "yaw")
14+
})
15+
16+
it("keeps astro pi component if code run without sense hat imported", () => {
17+
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', '')
18+
cy.get("editor-wc").shadow().find(".btn--run").click()
19+
cy.get("editor-wc").shadow().find("#root").should("contain", "yaw")
520
})
621

722
it("loads the sense hat library", () => {
@@ -57,15 +72,17 @@ it("confirms LEDs used when single led set", () => {
5772
it("confirms LEDs used when display set", () => {
5873
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)')
5974
cy.get("editor-wc").shadow().find(".btn--run").click()
75+
cy.scrollTo('bottom')
6076
cy.get("#results").should("contain", '"usedLEDs":true')
6177
})
6278

63-
// it("picks up calls to input()", () => {
64-
// cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'input()')
65-
// cy.get("editor-wc").shadow().find(".btn--run").click()
66-
// cy.get("editor-wc").shadow().find("span[contenteditable=true]").type('{enter}')
67-
// cy.get("#results").should("contain", '"noInputEvents":false')
68-
// })
79+
it("picks up calls to input()", () => {
80+
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'input()')
81+
cy.get("editor-wc").shadow().find(".btn--run").click()
82+
cy.get("editor-wc").shadow().contains('Text Output').click()
83+
cy.get("editor-wc").shadow().find("span[contenteditable=true]").type('{enter}')
84+
cy.get("#results").should("contain", '"noInputEvents":false')
85+
})
6986

7087
it("picks up calls to wait for motion", () => {
7188
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'from sense_hat import SenseHat\nsense = SenseHat()\nsense.motion.wait_for_motion()')

cypress/e2e/spec-wc.cy.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,34 @@ it("renders the web component", () => {
88
cy.get("editor-wc").shadow().find("button").should("contain", "Run")
99
})
1010

11+
it("defaults to the text output tab", () => {
12+
const runnerContainer = cy.get("editor-wc").shadow().find('.proj-runner-container')
13+
runnerContainer.find('.react-tabs__tab--selected').should("contain", "Text Output")
14+
})
15+
1116
it("runs the python code", () => {
1217
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'print("Hello world")')
1318
cy.get("editor-wc").shadow().find(".btn--run").click()
1419
cy.get("editor-wc").shadow().find(".pythonrunner-console-output-line").should("contain", "Hello world")
1520
})
21+
22+
it("does not render the astro pi component on page load", () => {
23+
cy.get("editor-wc").shadow().contains('Visual Output').click()
24+
cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw")
25+
})
26+
27+
it("renders astro pi component if sense hat imported", () => {
28+
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'import sense_hat')
29+
cy.get("editor-wc").shadow().find(".btn--run").click()
30+
cy.get("editor-wc").shadow().contains('Visual Output').click()
31+
cy.get("editor-wc").shadow().find("#root").should("contain", "yaw")
32+
})
33+
34+
it("does not render astro pi component if sense hat unimported", () => {
35+
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'import sense_hat')
36+
cy.get("editor-wc").shadow().find(".btn--run").click()
37+
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', '')
38+
cy.get("editor-wc").shadow().find(".btn--run").click()
39+
cy.get("editor-wc").shadow().contains('Visual Output').click()
40+
cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw")
41+
})

src/components/Editor/EditorSlice.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const EditorSlice = createSlice({
1313
codeRunStopped: false,
1414
projectList: [],
1515
projectListLoaded: false,
16+
senseHatAlwaysEnabled: false,
1617
},
1718
reducers: {
1819
updateImages: (state, action) => {
@@ -38,6 +39,9 @@ export const EditorSlice = createSlice({
3839
setProjectLoaded: (state, action) => {
3940
state.projectLoaded = action.payload;
4041
},
42+
setSenseHatAlwaysEnabled: (state, action) => {
43+
state.senseHatAlwaysEnabled = action.payload;
44+
},
4145
triggerDraw: (state) => {
4246
state.drawTriggered = true;
4347
},
@@ -99,6 +103,7 @@ export const {
99103
setProjectList,
100104
setProjectListLoaded,
101105
setProjectLoaded,
106+
setSenseHatAlwaysEnabled,
102107
stopCodeRun,
103108
stopDraw,
104109
triggerCodeRun,

src/components/Editor/Runners/PythonRunner/PythonRunner.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ const PythonRunner = () => {
1616
const codeRunTriggered = useSelector((state) => state.editor.codeRunTriggered);
1717
const codeRunStopped = useSelector((state) => state.editor.codeRunStopped);
1818
const drawTriggered = useSelector((state) => state.editor.drawTriggered);
19+
const senseHatAlwaysEnabled = useSelector((state) => state.editor.senseHatAlwaysEnabled);
1920
const outputCanvas = useRef();
2021
const output = useRef();
2122
const pygalOutput = useRef();
2223
const p5Output = useRef();
23-
const senseHatContainer = useRef();
2424
const dispatch = useDispatch();
2525

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

9898
if (x==="./_internal_sense_hat/__init__.js") {
9999
setSenseHatEnabled(true)
100-
senseHatContainer.current.hidden=false
101100
}
102101

103102
let localProjectFiles = projectCode.filter((component) => component.name !== 'main').map((component) => `./${component.name}.py`);
@@ -226,7 +225,8 @@ const PythonRunner = () => {
226225
output.current.innerHTML = '';
227226
pygalOutput.current.innerHTML = '';
228227
p5Output.current.innerHTML = '';
229-
senseHatContainer.current.hidden = true
228+
229+
setSenseHatEnabled(false)
230230

231231
var prog = projectCode[0].content;
232232

@@ -299,7 +299,7 @@ const PythonRunner = () => {
299299

300300
return (
301301
<div className="pythonrunner-container">
302-
<Tabs forceRenderTabPanel={true} defaultIndex={1}>
302+
<Tabs forceRenderTabPanel={true} defaultIndex={senseHatAlwaysEnabled ? 0 : 1}>
303303
<TabList>
304304
<Tab key={0}>Visual Output</Tab>
305305
<Tab key={1}>Text Output</Tab>
@@ -312,7 +312,7 @@ const PythonRunner = () => {
312312
<div className="pythonrunner-canvas-container">
313313
<div id='outputCanvas' ref={outputCanvas} className="pythonrunner-graphic" />
314314
</div>
315-
<div id='senseHatCanvas' ref={senseHatContainer} hidden={true}>{senseHatEnabled?<AstroPiModel/>:null}</div>
315+
<div id='senseHatCanvas'>{senseHatEnabled || senseHatAlwaysEnabled ?<AstroPiModel/>:null}</div>
316316
</div>
317317
</TabPanel>
318318
<TabPanel key={1}>

src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ exports[`Renders without crashing 1`] = `
9191
/>
9292
</div>
9393
<div
94-
hidden=""
9594
id="senseHatCanvas"
9695
/>
9796
</div>

src/components/WebComponent/WebComponentLoader/WebComponentLoader.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import React, { useEffect } from 'react';
22
import { useSelector, useDispatch } from 'react-redux'
3-
import { setProject, setProjectLoaded } from '../../Editor/EditorSlice';
3+
import { setProject, setProjectLoaded, setSenseHatAlwaysEnabled } from '../../Editor/EditorSlice';
44
import WebComponentProject from '../Project/WebComponentProject';
55

66
const ProjectComponentLoader = (props) => {
77
const projectLoaded = useSelector((state) => state.editor.projectLoaded);
8-
const { code } = props;
8+
const { code, sense_hat_always_enabled } = props;
99
const dispatch = useDispatch()
1010

1111
useEffect(() => {
1212
const proj = {
1313
type: 'python',
1414
components: [{ name: 'main', extension: 'py', content: code }]
1515
}
16+
dispatch(setSenseHatAlwaysEnabled(typeof sense_hat_always_enabled !== 'undefined'))
1617
dispatch(setProject(proj))
1718
dispatch(setProjectLoaded(true))
1819
}, []);

src/web-component.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
<title>Editor Web component</title>
77
</head>
88
<body>
9-
<editor-wc code=""></editor-wc>
109
<p id="results"></p>
1110
</body>
1211

1312
<script>
1413
document.addEventListener('DOMContentLoaded', (e) => {
15-
const webComp = document.querySelector('editor-wc');
14+
const webComp = document.createElement('editor-wc')
15+
const queryParams = new URLSearchParams(window.location.search)
16+
17+
// Pre-set the code attribute with an empty string.
18+
webComp.setAttribute('code', '')
19+
// Set any attribute you like in the query string, including class, style, hidden, script, etc.
20+
queryParams.forEach((value, key) => { webComp.setAttribute(key, value) })
1621

1722
// subscribe to the 'codeChanged' custom event which is pushed by the project react component
1823
webComp.addEventListener('codeChanged', function(e) {
@@ -26,6 +31,8 @@
2631
console.log(e.detail)
2732
document.getElementById("results").innerText = JSON.stringify(e.detail)
2833
})
34+
35+
document.getElementsByTagName('body')[0].prepend(webComp)
2936
});
3037
</script>
3138
</html>

src/web-component.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class WebComponent extends HTMLElement {
2020
}
2121

2222
static get observedAttributes() {
23-
return ['code'];
23+
return ['code', 'sense_hat_always_enabled'];
2424
}
2525

2626
attributeChangedCallback(name, _oldVal, newVal) {

0 commit comments

Comments
 (0)