Skip to content

Commit 1678672

Browse files
authored
Merge branch 'sugarlabs:master' into master
2 parents 29a4416 + f880553 commit 1678672

37 files changed

+12306
-6829
lines changed

css/themes.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@
104104
color: white;
105105
}
106106

107+
.dark .dropdown-content li > a {
108+
background-color: #1c1c1c;
109+
color: #3fe0d1;
110+
}
111+
112+
.dark .dropdown-content li > a:hover {
113+
background-color: #333;
114+
}
107115

116+
.dark .dropdown-content {
117+
background-color: #1c1c1c;
118+
}
108119

109120
/* Your Custom Theme can go here if you don't want to modify the existing dark mode */

cypress.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const { defineConfig } = require("cypress");
2+
3+
module.exports = defineConfig({
4+
e2e: {
5+
setupNodeEvents(on, config) {
6+
// implement node event listeners here
7+
},
8+
viewportWidth: 1400,
9+
viewportHeight: 1000,
10+
testIsolation: false
11+
},
12+
});

cypress/e2e/main.cy.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
Cypress.on("uncaught:exception", (err, runnable) => {
2+
return false;
3+
});
4+
5+
describe("MusicBlocks Application", () => {
6+
before(() => {
7+
cy.visit("http://localhost:3000");
8+
});
9+
10+
afterEach(() => {
11+
console.log("Next test running, no reload should happen");
12+
});
13+
14+
describe("Loading and Initial Render", () => {
15+
it("should display the loading animation and then the main content", () => {
16+
cy.get("#loading-image-container").should("be.visible");
17+
cy.contains("#loadingText", "Loading Complete!", { timeout: 20000 }).should("be.visible");
18+
cy.wait(10000);
19+
cy.get("#canvas", { timeout: 10000 }).should("be.visible");
20+
});
21+
22+
it("should display the Musicblocks guide page", () => {
23+
cy.get(".heading").contains("Welcome to Music Blocks");
24+
});
25+
});
26+
27+
describe("Audio Controls", () => {
28+
it("should have a functional play button", () => {
29+
cy.get("#play").should("be.visible").click();
30+
cy.window().then((win) => {
31+
const audioContext = win.Tone.context;
32+
cy.wrap(audioContext.state).should("eq", "running");
33+
});
34+
});
35+
36+
it("should have a functional stop button", () => {
37+
cy.get("#stop").should("be.visible").click();
38+
});
39+
});
40+
41+
describe("Toolbar and Navigation", () => {
42+
it("should open the language selection dropdown", () => {
43+
cy.get("#aux-toolbar").invoke("show");
44+
cy.get("#languageSelectIcon").click({ force: true });
45+
cy.get("#languagedropdown").should("be.visible");
46+
});
47+
48+
it("should toggle full-screen mode", () => {
49+
cy.get("#FullScreen").click();
50+
cy.document().its("fullscreenElement").should("exist");
51+
cy.get("#FullScreen").click();
52+
cy.document().its("fullscreenElement").should("not.exist");
53+
});
54+
55+
it("should toggle the toolbar menu", () => {
56+
cy.get("#toggleAuxBtn").click();
57+
cy.get("#aux-toolbar").should("be.visible");
58+
cy.get("#toggleAuxBtn").click();
59+
cy.get("#aux-toolbar").should("not.be.visible");
60+
});
61+
});
62+
63+
describe("File Operations", () => {
64+
it("should open the file load modal", () => {
65+
cy.get("#load").click();
66+
cy.get("#myOpenFile").should("exist");
67+
});
68+
69+
it("should open the save dropdown", () => {
70+
cy.get("#saveButton").click();
71+
cy.get("#saveddropdownbeg").should("be.visible");
72+
});
73+
74+
it("should display file save options", () => {
75+
cy.get("#saveButton").click();
76+
cy.get("#saveddropdownbeg").should("be.visible");
77+
cy.get("#save-html-beg").should("exist");
78+
cy.get("#save-png-beg").should("exist");
79+
});
80+
81+
it('should click the New File button and verify "New Project" appears', () => {
82+
cy.get('#newFile > .material-icons')
83+
.should('exist')
84+
.and('be.visible');
85+
cy.get('#newFile > .material-icons').click();
86+
cy.wait(500);
87+
cy.contains('New project').should('be.visible');
88+
});
89+
});
90+
91+
describe("UI Elements", () => {
92+
it('should verify that bottom bar elements exist and are visible', () => {
93+
const bottomBarElements = [
94+
'#Home\\ \\[HOME\\] > img',
95+
'#Show\\/hide\\ blocks > img',
96+
'#Expand\\/collapse\\ blocks > img',
97+
'#Decrease\\ block\\ size > img',
98+
'#Increase\\ block\\ size > img'
99+
];
100+
101+
bottomBarElements.forEach(selector => {
102+
cy.get(selector).should('exist').and('be.visible');
103+
});
104+
});
105+
106+
it('should verify sidebar elements exist, are visible, and clickable', () => {
107+
const sidebarElements = [
108+
'thead > tr > :nth-child(1) > img',
109+
'tr > :nth-child(2) > img',
110+
'tr > :nth-child(3) > img'
111+
];
112+
113+
sidebarElements.forEach(selector => {
114+
cy.get(selector)
115+
.should('exist')
116+
.and('be.visible')
117+
.click();
118+
});
119+
});
120+
121+
it('should verify that Grid, Clear, and Collapse elements exist and are visible', () => {
122+
const elements = [
123+
'#Grid > img',
124+
'#Clear',
125+
'#Collapse > img'
126+
];
127+
elements.forEach(selector => {
128+
cy.get(selector).should('exist').and('be.visible');
129+
});
130+
});
131+
132+
it('should verify that all nth-child elements from 1 to 6 exist', () => {
133+
for (let i = 1; i <= 6; i++) {
134+
cy.get(`[width="126"] > tbody > :nth-child(${i})`)
135+
.should('exist')
136+
.and('be.visible');
137+
}
138+
});
139+
});
140+
141+
describe("Planet Page Interaction", () => {
142+
it('should load the Planet page and return to the main page when clicking the close button', () => {
143+
cy.get('#planetIcon > .material-icons')
144+
.should('exist')
145+
.and('be.visible')
146+
.click();
147+
cy.get('#planet-iframe')
148+
.should('be.visible');
149+
});
150+
});
151+
152+
});

cypress/fixtures/example.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "Using fixtures to represent data",
3+
"email": "[email protected]",
4+
"body": "Fixtures are a great way to mock data for responses to routes"
5+
}

cypress/support/commands.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// ***********************************************
2+
// This example commands.js shows you how to
3+
// create various custom commands and overwrite
4+
// existing commands.
5+
//
6+
// For more comprehensive examples of custom
7+
// commands please read more here:
8+
// https://on.cypress.io/custom-commands
9+
// ***********************************************
10+
//
11+
//
12+
// -- This is a parent command --
13+
// Cypress.Commands.add('login', (email, password) => { ... })
14+
//
15+
//
16+
// -- This is a child command --
17+
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
18+
//
19+
//
20+
// -- This is a dual command --
21+
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
22+
//
23+
//
24+
// -- This will overwrite an existing command --
25+
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

cypress/support/e2e.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// ***********************************************************
2+
// This example support/e2e.js is processed and
3+
// loaded automatically before your test files.
4+
//
5+
// This is a great place to put global configuration and
6+
// behavior that modifies Cypress.
7+
//
8+
// You can change the location of this file or turn off
9+
// automatically serving support files with the
10+
// 'supportFile' configuration option.
11+
//
12+
// You can read more here:
13+
// https://on.cypress.io/configuration
14+
// ***********************************************************
15+
16+
// Import commands.js using ES2015 syntax:
17+
import './commands'

dist/css/keyboard.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,30 @@
88
cursor: pointer;
99
position: relative;
1010
}
11+
12+
#countdownContainer {
13+
position: absolute;
14+
top: 0;
15+
left: 0;
16+
width: 100%;
17+
height: 100%;
18+
z-index: 200;
19+
background: rgba(53, 53, 53, 0.492);
20+
pointer-events: all;
21+
}
22+
23+
#countdownDisplay {
24+
position: absolute;
25+
top: 50%;
26+
left: 50%;
27+
transform: translate(-50%, -50%);
28+
background: black;
29+
color: white;
30+
padding: 20px;
31+
border-radius: 10px;
32+
text-align: center;
33+
}
34+
1135
table {
1236
table-layout: fixed;
1337
}

documentation/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ The **Main** toolbar is located across the top of the screen. It contains severa
102102

103103
- **Play Button**: Starts playing the project.
104104
- **Stop Button**: Stops the current project playback.
105+
- **Fullscreen Button**: Toggles fullscreen mode on or off.
105106
- **New Project Button**: Creates a new project from scratch.
106107
- **Load Project from File Button**: Opens an existing project file.
107108
- **Save Project Button**: Saves the current project.
@@ -117,6 +118,7 @@ The **Secondary** toolbar appears when you click the **hamburger button** (three
117118

118119
- **Run Slowly**: Executes the program slowly to allow you to follow the process step by step.
119120
- **Run Step by Step**: Runs the program one step at a time, ideal for debugging and analysis.
121+
- **Change theme**: Switch between light and dark mode for a customized workspace.
120122
- **Merge with Current Project**: Combines the current project with another, promoting collaboration and reusability.
121123
- **Turtle Wrap**: Enables wrapping of the turtle's position to seamlessly continue from the opposite edge of the canvas.
122124
- **Set Pitch Preview**: Lets users preview pitches while composing, providing instant feedback.
@@ -128,7 +130,7 @@ The **Secondary** toolbar appears when you click the **hamburger button** (three
128130
3. *Delete Plugin*: Allows the removal of plugins that are no longer needed.<br>
129131
4. *Horizontal Scrolling*: Enables horizontal navigation for easier handling of large projects.<br>
130132
5. *JavaScript Editor*: Includes an editor for writing and embedding custom JavaScript code.<br>
131-
6.*Record*: Adds a "Record" button to the main palette, enabling users to record their compositions directly.
133+
6. *Record*: Adds a "Record" button to the main palette, enabling users to record their compositions directly.
132134

133135
- **Select Language**: Offers a multilingual interface, allowing users to change the language as per their preference.
134136

js/SaveInterface.js

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -293,64 +293,12 @@ class SaveInterface {
293293
* @instance
294294
*/
295295
afterSaveMIDI() {
296+
const instrumentMIDI = getMidiInstrument();
297+
const drumMIDI = getMidiDrum();
296298
const generateMidi = (data) => {
297299
const normalizeNote = (note) => {
298300
return note.replace("♯", "#").replace("♭", "b");
299301
};
300-
const MIDI_INSTRUMENTS = {
301-
default: 0, // Acoustic Grand Piano
302-
piano: 0,
303-
violin: 40,
304-
viola: 41,
305-
cello: 42,
306-
"double bass": 43,
307-
bass: 32,
308-
sitar: 104,
309-
guitar: 24,
310-
"acoustic guitar": 25,
311-
"electric guitar": 27,
312-
flute: 73,
313-
clarinet: 71,
314-
saxophone: 65,
315-
tuba: 58,
316-
trumpet: 56,
317-
oboe: 68,
318-
trombone: 57,
319-
banjo: 105,
320-
koto: 107,
321-
dulcimer: 15,
322-
bassoon: 70,
323-
celeste: 8,
324-
xylophone: 13,
325-
"electronic synth": 81,
326-
sine: 81, // Approximate with Lead 2 (Sawtooth)
327-
square: 80,
328-
sawtooth: 81,
329-
triangle: 81, // Approximate with Lead 2 (Sawtooth)
330-
vibraphone: 11
331-
};
332-
333-
const DRUM_MIDI_MAP = {
334-
"snare drum": 38,
335-
"kick drum": 36,
336-
"tom tom": 41,
337-
"floor tom tom": 43,
338-
"cup drum": 47, // Closest: Low-Mid Tom
339-
"darbuka drum": 50, // Closest: High Tom
340-
"japanese drum": 56, // Closest: Cowbell or Tambourine
341-
"hi hat": 42,
342-
"ride bell": 53,
343-
"cow bell": 56,
344-
"triangle bell": 81,
345-
"finger cymbals": 69, // Closest: Open Hi-Hat
346-
"chime": 82, // Closest: Shaker
347-
"gong": 52, // Closest: Chinese Cymbal
348-
"clang": 55, // Closest: Splash Cymbal
349-
"crash": 49,
350-
"clap": 39,
351-
"slap": 40,
352-
"raindrop": 88 // Custom mapping (not in GM), can use melodic notes
353-
};
354302

355303
const midi = new Midi();
356304
midi.header.ticksPerBeat = 480;
@@ -379,7 +327,7 @@ class SaveInterface {
379327

380328
const drumTrack = trackMap.get(drum);
381329

382-
const midiNumber = DRUM_MIDI_MAP[drum] || 36; // default to Bass Drum
330+
const midiNumber = drumMIDI[drum] || 36; // default to Bass Drum
383331
drumTrack.addNote({
384332
midi: midiNumber,
385333
time: globalTime,
@@ -391,7 +339,7 @@ class SaveInterface {
391339
if (!trackMap.has(instrument)) {
392340
const instrumentTrack = midi.addTrack();
393341
instrumentTrack.name = `Track ${parseInt(blockIndex) + 1} - ${instrument}`;
394-
instrumentTrack.instrument.number = MIDI_INSTRUMENTS[instrument] ?? MIDI_INSTRUMENTS["default"];
342+
instrumentTrack.instrument.number = instrumentMIDI[instrument] ?? instrumentMIDI["default"];
395343
trackMap.set(instrument, instrumentTrack);
396344
}
397345

@@ -809,7 +757,7 @@ class SaveInterface {
809757
this.activity.logo.runLogoCommands();
810758
}
811759

812-
/**
760+
/**
813761
* Perform actions after saving an MXML file.
814762
*
815763
* This method handles post-processing steps after saving an MXML file.
@@ -827,3 +775,8 @@ class SaveInterface {
827775
this.activity.logo.runningMxml = false;
828776
}
829777
}
778+
779+
780+
if (typeof module !== 'undefined' && module.exports) {
781+
module.exports = { SaveInterface };
782+
}

0 commit comments

Comments
 (0)