Skip to content

Commit 1f80392

Browse files
authored
Merge branch 'sugarlabs:master' into unwanted-black-keys
2 parents ae99427 + bfb52c5 commit 1f80392

File tree

15 files changed

+1107
-266
lines changed

15 files changed

+1107
-266
lines changed

jest.config.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module.exports = {
2+
// Use jsdom to simulate a browser environment
3+
testEnvironment: 'jest-environment-jsdom',
4+
5+
// Specify where Jest should look for test files
6+
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
7+
8+
// Transform JavaScript files using Babel (if needed)
9+
transform: {
10+
'^.+\\.jsx?$': 'babel-jest',
11+
},
12+
13+
// Clear mocks between tests for isolation
14+
clearMocks: true,
15+
16+
// Collect coverage information and specify the directory
17+
collectCoverage: true,
18+
coverageDirectory: 'coverage',
19+
20+
// Specify file extensions Jest will process
21+
moduleFileExtensions: ['js', 'jsx', 'json', 'node'],
22+
23+
// Define any global variables for the tests
24+
globals: {
25+
'window': {},
26+
},
27+
// Set up files to run before tests (e.g., polyfills, setup scripts)
28+
setupFiles: ['./jest.setup.js'], // Optional, if you have a setup file
29+
};

jest.setup.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// A jest.setup.js file is used to run setup code before your tests are executed.

js/activity.js

Lines changed: 113 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,54 +1017,121 @@ class Activity {
10171017
/*
10181018
* Clears "canvas"
10191019
*/
1020+
const renderClearConfirmation = (clearCanvasAction) => {
1021+
// Create a custom modal for confirmation
1022+
const modal = document.createElement("div");
1023+
modal.style.position = "fixed";
1024+
modal.style.top = "50%";
1025+
modal.style.left = "50%";
1026+
modal.style.transform = "translate(-50%, -50%)";
1027+
modal.style.width = "400px";
1028+
modal.style.padding = "24px";
1029+
modal.style.backgroundColor = "#fff";
1030+
modal.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.2)";
1031+
modal.style.borderRadius = "8px";
1032+
modal.style.zIndex = "10000";
1033+
modal.style.textAlign = "left";
1034+
const title = document.createElement("h2");
1035+
title.textContent = "Clear Workspace";
1036+
title.style.color = "#0066FF";
1037+
title.style.fontSize = "24px";
1038+
title.style.margin = "0 0 16px 0";
1039+
modal.appendChild(title);
1040+
const message = document.createElement("p");
1041+
message.textContent = "Are you sure you want to clear the workspace?";
1042+
message.style.color = "#666666";
1043+
message.style.fontSize = "16px";
1044+
message.style.marginBottom = "24px";
1045+
modal.appendChild(message);
1046+
// Add buttons
1047+
const buttonContainer = document.createElement("div");
1048+
buttonContainer.style.display = "flex";
1049+
buttonContainer.style.justifyContent = "flex-start";
1050+
1051+
const confirmBtn = document.createElement("button");
1052+
confirmBtn.textContent = "Confirm";
1053+
confirmBtn.style.backgroundColor = "#2196F3";
1054+
confirmBtn.style.color = "white";
1055+
confirmBtn.style.border = "none";
1056+
confirmBtn.style.borderRadius = "4px";
1057+
confirmBtn.style.padding = "8px 16px";
1058+
confirmBtn.style.fontWeight = "bold";
1059+
confirmBtn.style.cursor = "pointer";
1060+
confirmBtn.style.marginRight = "16px";
1061+
confirmBtn.addEventListener("click", () => {
1062+
document.body.removeChild(modal);
1063+
clearCanvasAction();
1064+
});
1065+
1066+
const cancelBtn = document.createElement("button");
1067+
cancelBtn.textContent = "Cancel";
1068+
cancelBtn.style.backgroundColor = "#f1f1f1";
1069+
cancelBtn.style.color = "black";
1070+
cancelBtn.style.border = "none";
1071+
cancelBtn.style.borderRadius = "4px";
1072+
cancelBtn.style.padding = "8px 16px";
1073+
cancelBtn.style.fontWeight = "bold";
1074+
cancelBtn.style.cursor = "pointer";
1075+
cancelBtn.addEventListener("click", () => {
1076+
document.body.removeChild(modal);
1077+
});
1078+
1079+
buttonContainer.appendChild(confirmBtn);
1080+
buttonContainer.appendChild(cancelBtn);
1081+
modal.appendChild(buttonContainer);
1082+
document.body.appendChild(modal);
1083+
};
1084+
10201085
this._allClear = (noErase) => {
1021-
this.blocks.activeBlock = null;
1022-
hideDOMLabel();
1023-
1024-
this.logo.boxes = {};
1025-
this.logo.time = 0;
1026-
this.hideMsgs();
1027-
this.hideGrids();
1028-
this.turtles.setBackgroundColor(-1);
1029-
this.logo.svgOutput = "";
1030-
this.logo.notationOutput = "";
1031-
for (let turtle = 0; turtle < this.turtles.turtleList.length; turtle++) {
1032-
this.logo.turtleHeaps[turtle] = [];
1033-
this.logo.turtleDicts[turtle] = {};
1034-
this.logo.notation.notationStaging[turtle] = [];
1035-
this.logo.notation.notationDrumStaging[turtle] = [];
1036-
if (noErase === undefined || !noErase) {
1037-
this.turtles.turtleList[turtle].painter.doClear(true, true, true);
1086+
const clearCanvasAction = () => {
1087+
this.blocks.activeBlock = null;
1088+
hideDOMLabel();
1089+
1090+
this.logo.boxes = {};
1091+
this.logo.time = 0;
1092+
this.hideMsgs();
1093+
this.hideGrids();
1094+
this.turtles.setBackgroundColor(-1);
1095+
this.logo.svgOutput = "";
1096+
this.logo.notationOutput = "";
1097+
for (let turtle = 0; turtle < this.turtles.turtleList.length; turtle++) {
1098+
this.logo.turtleHeaps[turtle] = [];
1099+
this.logo.turtleDicts[turtle] = {};
1100+
this.logo.notation.notationStaging[turtle] = [];
1101+
this.logo.notation.notationDrumStaging[turtle] = [];
1102+
if (noErase === undefined || !noErase) {
1103+
this.turtles.turtleList[turtle].painter.doClear(true, true, true);
1104+
}
10381105
}
1039-
}
1040-
1041-
this.blocksContainer.x = 0;
1042-
this.blocksContainer.y = 0;
1043-
1044-
// Code specific to cleaning up Music Blocks
1045-
Element.prototype.remove = () => {
1046-
this.parentElement.removeChild(this);
1047-
};
1048-
1049-
NodeList.prototype.remove = HTMLCollection.prototype.remove = () => {
1050-
for (let i = 0, len = this.length; i < len; i++) {
1051-
if (this[i] && this[i].parentElement) {
1052-
this[i].parentElement.removeChild(this[i]);
1106+
1107+
this.blocksContainer.x = 0;
1108+
this.blocksContainer.y = 0;
1109+
1110+
Element.prototype.remove = () => {
1111+
this.parentElement.removeChild(this);
1112+
};
1113+
1114+
NodeList.prototype.remove = HTMLCollection.prototype.remove = () => {
1115+
for (let i = 0, len = this.length; i < len; i++) {
1116+
if (this[i] && this[i].parentElement) {
1117+
this[i].parentElement.removeChild(this[i]);
1118+
}
10531119
}
1120+
};
1121+
1122+
const table = docById("myTable");
1123+
if (table !== null) {
1124+
table.remove();
1125+
}
1126+
1127+
if (docById("helpfulWheelDiv").style.display !== "none") {
1128+
docById("helpfulWheelDiv").style.display = "none";
1129+
this.__tick();
10541130
}
10551131
};
1056-
1057-
const table = docById("myTable");
1058-
if (table !== null) {
1059-
table.remove();
1060-
}
1061-
1062-
if (docById("helpfulWheelDiv").style.display !== "none") {
1063-
docById("helpfulWheelDiv").style.display = "none";
1064-
this.__tick();
1065-
}
1132+
1133+
renderClearConfirmation(clearCanvasAction);
10661134
};
1067-
10681135
/**
10691136
* Sets up play button functionality; runs Music Blocks.
10701137
* @param env {specifies environment}
@@ -1447,6 +1514,8 @@ class Activity {
14471514
else if (ele.label === "Disable horizontal scrolling")
14481515
ele.display = true;
14491516
})
1517+
activity.textMsg(("Horizontal scrolling enabled."), 3000);
1518+
14501519
} else {
14511520
enableHorizScrollIcon.style.display = "block";
14521521
disableHorizScrollIcon.style.display = "none";
@@ -1457,6 +1526,8 @@ class Activity {
14571526
else if (ele.label === "Disable horizontal scrolling")
14581527
ele.display = false;
14591528
})
1529+
activity.textMsg(("Horizontal scrolling disabled."), 3000);
1530+
14601531
}
14611532
};
14621533

js/base64Utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ function base64Decode(str) {
2828
}
2929

3030
export default { base64Encode, base64Decode };
31+
//module.exports = { base64Encode, base64Decode };

js/blocks/RhythmBlockPaletteBlocks.js

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,15 @@ function setupRhythmBlockPaletteBlocks(activity) {
277277
* Constructs a SixtyFourthNoteBlock.
278278
*/
279279
constructor() {
280-
super("sixtyfourthNote", _("1/64 note") + " 𝅘𝅥𝅱");
280+
if (isAppleBrowser()) {
281+
super("sixtyfourthNote", _("1/64 note"));
282+
// Custom generator only needed for Apple devices
283+
this.appleNoteBlock();
284+
} else {
285+
// TRANS: Do not modify the following line
286+
super("sixtyfourthNote", _("1/64 note") + " 𝅘𝅥𝅱");
287+
}
288+
281289
this.setPalette(rhythmBlockPalette, activity);
282290
this.setHelpString();
283291
this.makeMacro((x, y) => [
@@ -300,8 +308,14 @@ function setupRhythmBlockPaletteBlocks(activity) {
300308
* Constructs a ThirtySecondNoteBlock instance.
301309
*/
302310
constructor() {
303-
// TRANS: Do not modify the following line
304-
super("thirtysecondNote", _("1/32 note") + " 𝅘𝅥𝅰");
311+
if (isAppleBrowser()) {
312+
super("thirtysecondNote", _("1/32 note"));
313+
// Custom generator only needed for Apple devices
314+
this.appleNoteBlock();
315+
} else {
316+
// TRANS: Do not modify the following line
317+
super("thirtysecondNote", _("1/32 note") + " 𝅘𝅥𝅰");
318+
}
305319

306320
// Set the palette and activity for the block
307321
this.setPalette(rhythmBlockPalette, activity);
@@ -328,8 +342,14 @@ function setupRhythmBlockPaletteBlocks(activity) {
328342
* Constructs a SixteenthNoteBlock instance.
329343
*/
330344
constructor() {
331-
// TRANS: Do not modify the following line
332-
super("sixteenthNote", _("1/16 note") + " 𝅘𝅥𝅯");
345+
if (isAppleBrowser()) {
346+
super("sixteenthNote", _("1/16 note"));
347+
// Custom generator only needed for Apple devices
348+
this.appleNoteBlock();
349+
} else {
350+
// TRANS: Do not modify the following line
351+
super("sixteenthNote", _("1/16 note") + " 𝅘𝅥𝅯");
352+
}
333353

334354
// Set the palette and activity for the block
335355
this.setPalette(rhythmBlockPalette, activity);
@@ -412,8 +432,14 @@ function setupRhythmBlockPaletteBlocks(activity) {
412432
* Constructs a HalfNoteBlock instance.
413433
*/
414434
constructor() {
415-
// TRANS: Do not modify the following line
416-
super("halfNote", _("half note") + " 𝅗𝅥");
435+
if (isAppleBrowser()) {
436+
super("halfNote", _("half note"));
437+
// Custom generator only needed for Apple devices
438+
this.appleNoteBlock();
439+
} else {
440+
// TRANS: Do not modify the following line
441+
super("halfNote", _("half note") + " 𝅗𝅥");
442+
}
417443

418444
// Set the palette and activity for the block
419445
this.setPalette(rhythmBlockPalette, activity);
@@ -440,8 +466,14 @@ function setupRhythmBlockPaletteBlocks(activity) {
440466
* Constructs a WholeNoteBlock instance.
441467
*/
442468
constructor() {
443-
// TRANS: Do not modify the following line
444-
super("wholeNote", _("whole note") + " 𝅝");
469+
if (isAppleBrowser()) {
470+
super("wholeNote", _("whole note"));
471+
// Custom generator only needed for Apple devices
472+
this.appleNoteBlock();
473+
} else {
474+
// TRANS: Do not modify the following line
475+
super("wholeNote", _("whole note") + " 𝅝");
476+
}
445477

446478
// Set the palette and activity for the block
447479
this.setPalette(rhythmBlockPalette, activity);
@@ -996,3 +1028,13 @@ function setupRhythmBlockPaletteBlocks(activity) {
9961028
new TripletBlock().setup(activity);
9971029
new STupletBlock().setup(activity);
9981030
}
1031+
1032+
// Function to check if the browser is Safari or Chrome on a Mac or iPad
1033+
function isAppleBrowser() {
1034+
const userAgent = navigator.userAgent;
1035+
const isMac = userAgent.includes('Macintosh');
1036+
const isIPad = userAgent.includes('iPad') || (userAgent.includes('Macintosh') && 'ontouchend' in document); // Detects iPad in desktop mode
1037+
const isSafari = userAgent.includes('Safari') && !userAgent.includes('Chrome');
1038+
const isChrome = userAgent.includes('Chrome');
1039+
return (isMac || isIPad) && (isSafari || isChrome);
1040+
}

0 commit comments

Comments
 (0)