Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTML audio video support #1179

Merged
merged 7 commits into from
Jan 30, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Ability to write to files in `python` (#1146)
- Support for the `outputPanels` attribute in the `PyodideRunner` (#1157)
- Downloading project instructions (#1160)
- Support for audio and video files in HTML projects (#1179)

### Changed

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
"@react-three/test-renderer": "8.2.1",
"@svgr/webpack": "5.5.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.2.0",
"@testing-library/react": "14.3.1",
"@testing-library/user-event": "^12.1.10",
"@typescript-eslint/eslint-plugin": "^4.5.0",
"@typescript-eslint/parser": "^4.5.0",
Expand Down
34 changes: 20 additions & 14 deletions src/components/Editor/Runners/HtmlRunner/HtmlRunner.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints";
function HtmlRunner() {
const project = useSelector((state) => state.editor.project);
const projectCode = project.components;
const projectImages = project.image_list;
const projectMedia = [
...(project.image_list || []),
...(project.audio || []),
...(project.videos || []),
];

const firstPanelIndex = 0;
const focussedFileIndex = useSelector(
Expand Down Expand Up @@ -114,9 +118,9 @@ function HtmlRunner() {
const cssProjectImgs = (projectFile) => {
var updatedProjectFile = { ...projectFile };
if (projectFile.extension === "css") {
projectImages.forEach((image) => {
const find = new RegExp(`['"]${image.filename}['"]`, "g"); // prevent substring matches
const replace = `"${image.url}"`;
projectMedia.forEach((media_file) => {
const find = new RegExp(`['"]${media_file.filename}['"]`, "g"); // prevent substring matches
const replace = `"${media_file.url}"`;
updatedProjectFile.content = updatedProjectFile.content.replaceAll(
find,
replace,
Expand Down Expand Up @@ -264,27 +268,29 @@ function HtmlRunner() {

const replaceSrcNodes = (
indexPage,
projectImages,
projectMedia,
projectCode,
attr = "src",
) => {
const srcNodes = indexPage.querySelectorAll(`[${attr}]`);

srcNodes.forEach((srcNode) => {
const projectImage = projectImages.find(
const projectMediaFile = projectMedia.find(
(component) => component.filename === srcNode.attrs[attr],
);
const projectFile = projectCode.find(
const projectTextFile = projectCode.find(
(file) => `${file.name}.${file.extension}` === srcNode.attrs[attr],
);

let src = "";
if (!!projectImage) {
src = projectImage.url;
} else if (!!projectFile) {
if (!!projectMediaFile) {
src = projectMediaFile.url;
} else if (!!projectTextFile) {
src = getBlobURL(
projectFile.content,
mimeTypes.lookup(`${projectFile.name}.${projectFile.extension}`),
projectTextFile.content,
mimeTypes.lookup(
`${projectTextFile.name}.${projectTextFile.extension}`,
),
);
} else if (matchingRegexes(allowedExternalLinks, srcNode.attrs[attr])) {
src = srcNode.attrs[attr];
Expand Down Expand Up @@ -351,8 +357,8 @@ function HtmlRunner() {
body.insertAdjacentHTML("afterbegin", disableLocalStorageScript);

replaceHrefNodes(indexPage, projectCode);
replaceSrcNodes(indexPage, projectImages, projectCode);
replaceSrcNodes(indexPage, projectImages, projectCode, "data-src");
replaceSrcNodes(indexPage, projectMedia, projectCode);
replaceSrcNodes(indexPage, projectMedia, projectCode, "data-src");

body.appendChild(parse(`<meta filename="${previewFile}" />`));

Expand Down
71 changes: 71 additions & 0 deletions src/components/Editor/Runners/HtmlRunner/HtmlRunner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,77 @@ describe("When an allowed external link is rendered", () => {
});
});

describe("When media is rendered", () => {
const mediaHTML =
'<head></head><body><img src="image.jpeg" /><video src="video.mp4" /><audio src="audio.mp3" /></body>';
let generatedHtml;
beforeEach(() => {
const middlewares = [];
const mockStore = configureStore(middlewares);
const initialState = {
editor: {
project: {
components: [
{ name: "index", extension: "html", content: mediaHTML },
],
image_list: [
{
filename: "image.jpeg",
url: "https://example.com/image.jpeg",
},
],
videos: [
{
filename: "video.mp4",
url: "https://example.com/video.mp4",
},
],
audio: [
{
filename: "audio.mp3",
url: "https://example.com/audio.mp3",
},
],
},
focussedFileIndices: [0],
openFiles: [["index.html"]],
codeRunTriggered: true,
codeHasBeenRun: true,
errorModalShowing: false,
},
};
const store = mockStore(initialState);
render(
<Provider store={store}>
<MemoryRouter>
<div id="app">
<HtmlRunner />
</div>
</MemoryRouter>
</Provider>,
);
[generatedHtml] = Blob.mock.calls[0][0];
});

test("Transforms image sources", () => {
expect(generatedHtml).toContain(
'<img src="https://example.com/image.jpeg"',
);
});

test("Transforms video sources", () => {
expect(generatedHtml).toContain(
'<video src="https://example.com/video.mp4"',
);
});

test("Transforms audio sources", () => {
expect(generatedHtml).toContain(
'<audio src="https://example.com/audio.mp3"',
);
});
});

describe("When on desktop", () => {
let store;

Expand Down
50 changes: 34 additions & 16 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ __metadata:
languageName: node
linkType: hard

"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.25.7, @babel/code-frame@npm:^7.5.5":
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.25.7, @babel/code-frame@npm:^7.5.5":
version: 7.25.7
resolution: "@babel/code-frame@npm:7.25.7"
dependencies:
Expand All @@ -123,6 +123,17 @@ __metadata:
languageName: node
linkType: hard

"@babel/code-frame@npm:^7.10.4":
version: 7.26.2
resolution: "@babel/code-frame@npm:7.26.2"
dependencies:
"@babel/helper-validator-identifier": ^7.25.9
js-tokens: ^4.0.0
picocolors: ^1.0.0
checksum: db13f5c42d54b76c1480916485e6900748bbcb0014a8aca87f50a091f70ff4e0d0a6db63cade75eb41fcc3d2b6ba0a7f89e343def4f96f00269b41b8ab8dd7b8
languageName: node
linkType: hard

"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.7, @babel/compat-data@npm:^7.25.8":
version: 7.25.8
resolution: "@babel/compat-data@npm:7.25.8"
Expand Down Expand Up @@ -352,6 +363,13 @@ __metadata:
languageName: node
linkType: hard

"@babel/helper-validator-identifier@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/helper-validator-identifier@npm:7.25.9"
checksum: 5b85918cb1a92a7f3f508ea02699e8d2422fe17ea8e82acd445006c0ef7520fbf48e3dbcdaf7b0a1d571fc3a2715a29719e5226636cb6042e15fe6ed2a590944
languageName: node
linkType: hard

"@babel/helper-validator-option@npm:^7.25.7":
version: 7.25.7
resolution: "@babel/helper-validator-option@npm:7.25.7"
Expand Down Expand Up @@ -2753,7 +2771,7 @@ __metadata:
"@svgr/webpack": 5.5.0
"@szhsin/react-menu": ^3.2.0
"@testing-library/jest-dom": ^5.16.5
"@testing-library/react": ^13.2.0
"@testing-library/react": 14.3.1
"@testing-library/user-event": ^12.1.10
"@typescript-eslint/eslint-plugin": ^4.5.0
"@typescript-eslint/parser": ^4.5.0
Expand Down Expand Up @@ -3494,9 +3512,9 @@ __metadata:
languageName: node
linkType: hard

"@testing-library/dom@npm:^8.5.0":
version: 8.20.1
resolution: "@testing-library/dom@npm:8.20.1"
"@testing-library/dom@npm:^9.0.0":
version: 9.3.4
resolution: "@testing-library/dom@npm:9.3.4"
dependencies:
"@babel/code-frame": ^7.10.4
"@babel/runtime": ^7.12.5
Expand All @@ -3506,7 +3524,7 @@ __metadata:
dom-accessibility-api: ^0.5.9
lz-string: ^1.5.0
pretty-format: ^27.0.2
checksum: 06fc8dc67849aadb726cbbad0e7546afdf8923bd39acb64c576d706249bd7d0d05f08e08a31913fb621162e3b9c2bd0dce15964437f030f9fa4476326fdd3007
checksum: dfd6fb0d6c7b4dd716ba3c47309bc9541b4a55772cb61758b4f396b3785efe2dbc75dc63423545c039078c7ffcc5e4b8c67c2db1b6af4799580466036f70026f
languageName: node
linkType: hard

Expand All @@ -3527,17 +3545,17 @@ __metadata:
languageName: node
linkType: hard

"@testing-library/react@npm:^13.2.0":
version: 13.4.0
resolution: "@testing-library/react@npm:13.4.0"
"@testing-library/react@npm:14.3.1":
version: 14.3.1
resolution: "@testing-library/react@npm:14.3.1"
dependencies:
"@babel/runtime": ^7.12.5
"@testing-library/dom": ^8.5.0
"@testing-library/dom": ^9.0.0
"@types/react-dom": ^18.0.0
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
checksum: 51ec548c1fdb1271089a5c63e0908f0166f2c7fcd9cacd3108ebbe0ce64cb4351812d885892020dc37608418cfb15698514856502b3cab0e5cc58d6cc1bd4a3e
checksum: b057d4c9db5a523acfc24d7bc4665a924ab8d6f252c7f51eecf7dd30f1239413e1134925fd5cc9cbdef80496af64c04e6719b2081f89fe05ba87e8c6305bcc16
languageName: node
linkType: hard

Expand Down Expand Up @@ -3936,11 +3954,11 @@ __metadata:
linkType: hard

"@types/react-dom@npm:^18.0.0":
version: 18.3.1
resolution: "@types/react-dom@npm:18.3.1"
dependencies:
"@types/react": "*"
checksum: ad28ecce3915d30dc76adc2a1373fda1745ba429cea290e16c6628df9a05fd80b6403c8e87d78b45e6c60e51df7a67add389ab62b90070fbfdc9bda8307d9953
version: 18.3.5
resolution: "@types/react-dom@npm:18.3.5"
peerDependencies:
"@types/react": ^18.0.0
checksum: 95c757684f71e761515c5a11299e5feec550c72bb52975487f360e6f0d359b26454c26eaf2ce45dd22748205aa9b2c2fe0abe7005ebcbd233a7615283ac39a7d
languageName: node
linkType: hard

Expand Down
Loading