Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c4cd0f7
[UPDATE]: transformed into a class to perform movement actions
jcasben May 8, 2025
a70ccf8
[UPDATE]: added array of scene objects
jcasben May 8, 2025
9748986
[FIX]: added redirect to home page when changing mode
jcasben May 8, 2025
3766b29
[UPDATE]: created list of scene objects where the user can select whi…
jcasben May 8, 2025
df65a4c
[UPDATE]: now a shadow is drawn on the selected object in the canvas
jcasben May 8, 2025
626013c
[UPDATE]: made changes to create the initial array of scene objects
jcasben May 8, 2025
a3acbf7
[UPDATE]: added new output actions from scene component
jcasben May 8, 2025
d2aba8c
[UPDATE]: deleted old comments
jcasben May 8, 2025
cbde8b9
[UPDATE]: provisional changes to execute code from multiple objects a…
jcasben May 8, 2025
f26b3d2
[UPDATE]: changed saving on memory that caused a bug
jcasben May 9, 2025
70a25cd
[UPDATE]: checked if the code is running to prevent the user of dragg…
jcasben May 9, 2025
98d137a
[UPDATE]: added a check on the first time that the objects are render…
jcasben May 9, 2025
32c5457
[UPDATE]: changed the way that it recognizes when all the code has fi…
jcasben May 9, 2025
7708991
[UPDATE]: redrained scene objects after retrieving them from local st…
jcasben May 9, 2025
981b1ea
[UPDATE]: when the view loads, select a default scene object if there…
jcasben May 9, 2025
d4df171
[UPDATE]: activated saving to local storage
jcasben May 9, 2025
2990e0e
[UPDATE]: created a method to generate code for an object
jcasben May 9, 2025
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
4 changes: 0 additions & 4 deletions src/app/components/blockly-editor/blockly-editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export class BlocklyEditorComponent implements AfterViewInit {
private modeService = inject(ModeService);

@ViewChild('blocklyDiv') blocklyDiv!: ElementRef;
//@ViewChild('blocklyArea') blocklyArea!: ElementRef;

@Input() toolbox = signal({
kind: 'flyoutToolbox',
Expand Down Expand Up @@ -84,12 +83,9 @@ export class BlocklyEditorComponent implements AfterViewInit {
});

this.workspace.registerButtonCallback('addNewBlock', () => {
//this.openBlocksModal();
this.openModal.emit();
});

//this.resizeBlockly();

const jsonWorkspace = JSON.parse(this.workspaceJSON);
Blockly.serialization.workspaces.load(jsonWorkspace, this.workspace);
}
Expand Down
15 changes: 12 additions & 3 deletions src/app/components/scene/scene.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div #scene class="h-full w-full">
<div #scene class="flex flex-col h-full w-full">
<div class="flex flex-row justify-center">
<blearn-button
icon="play"
Expand All @@ -15,7 +15,7 @@
teacherText="Stop"
studentStyle="bg-red-500 text-white"
teacherStyle="bg-red-500 text-white"
(clicked)="stopCode.emit()"
(clicked)="isRunning.set(false)"
[disabled]="!isRunning()"
/>
@if (modeService.getMode() === 'teacher') {
Expand All @@ -28,5 +28,14 @@
}
</div>

<canvas #canvas class="w-full h-full bg-gray-300"></canvas>
<div class="flex flex-row space-x-4">
@for (obj of sceneObjects; track obj.id) {
<img [src]="obj.imgSrc"
[alt]="obj.id"
[ngClass]="['w-24 h-24 rounded', selectedObject() === obj.id ? 'border-4 border-red-500' : '']"
(click)="objectSelected.emit(obj.id)">
}
</div>

<canvas #canvas class="w-full h-screen bg-gray-300"></canvas>
</div>
100 changes: 50 additions & 50 deletions src/app/components/scene/scene.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import {
import {SceneObject} from '../../models/scene-object';
import {ButtonComponent} from '../button/button.component';
import {ModeService} from '../../services/mode.service';
import {NgClass} from '@angular/common';

@Component({
selector: 'blearn-scene',
imports: [
ButtonComponent
ButtonComponent,
NgClass
],
templateUrl: './scene.component.html',
})
Expand All @@ -27,11 +29,15 @@ export class SceneComponent implements AfterViewInit {
@ViewChild('scene') scene!: ElementRef;

@Input() isRunning = signal<boolean>(false);
@Input() sceneObjects: SceneObject[] = [];
@Input() selectedObject = signal<string | undefined>(undefined);

@Output() runCode = new EventEmitter<void>();
@Output() stopCode = new EventEmitter<void>();
@Output() sceneObjectsChange = new EventEmitter<void>();
@Output() objectAdded = new EventEmitter<void>();
@Output() objectSelected = new EventEmitter<string>();

private ctx: CanvasRenderingContext2D | null = null;
protected sceneObjects: Array<SceneObject> = [];
private draggingObject: SceneObject | null = null;
private offsetX: number = 0;
private offsetY: number = 0;
Expand All @@ -47,35 +53,32 @@ export class SceneComponent implements AfterViewInit {
this.canvas.nativeElement.width = this.scene.nativeElement.offsetWidth;
this.canvas.nativeElement.height = this.scene.nativeElement.offsetHeight;

// Create an image object
const img = new Image();
img.src = 'https://avatars.githubusercontent.com/u/105555875?v=4'; // Replace with your image URL
img.onload = () => {
// Once the image is loaded, draw it to the canvas
this.sceneObjects.push({img, x: 50, y: 50, rotation: 0, width: 100, height: 100});
this.drawImages();
};
const imageLoadPromises = this.sceneObjects.map(obj => {
return new Promise<void>((resolve) => {
const img = new Image();
img.src = obj.imgSrc;
img.onload = () => {
obj.img = img;
resolve();
}
});
});

// Set up mouse event listeners for dragging
Promise.all(imageLoadPromises).then(() => this.drawImages());
this.setupMouseEvents();
}

protected addObject() {
const img = new Image();
img.src = 'https://avatars.githubusercontent.com/u/105555875?v=4'; // Replace with your image URL
img.onload = () => {
// Once the image is loaded, draw it to the canvas
this.sceneObjects.push({img, x: 50, y: 50, rotation: 0, width: 100, height: 100});
this.drawImages();
};
this.objectAdded.emit();
}

private setupMouseEvents() {
this.canvas.nativeElement.addEventListener('mousedown', (e: MouseEvent) => {
if (this.isRunning()) return;

const mouseX = e.offsetX;
const mouseY = e.offsetY;

// Check if the mouse click is on one of the images
for (let sceneObj of this.sceneObjects) {
if (mouseX >= sceneObj.x && mouseX <= sceneObj.x + sceneObj.width && mouseY >= sceneObj.y && mouseY <= sceneObj.y + sceneObj.height) {
this.draggingObject = sceneObj;
Expand All @@ -86,6 +89,8 @@ export class SceneComponent implements AfterViewInit {
});

this.canvas.nativeElement.addEventListener('mousemove', (e: MouseEvent) => {
if (this.isRunning()) return;

if (this.draggingObject) {
const mouseX = e.offsetX;
const mouseY = e.offsetY;
Expand All @@ -99,50 +104,45 @@ export class SceneComponent implements AfterViewInit {
});

this.canvas.nativeElement.addEventListener('mouseup', () => {
if (this.isRunning()) return;

if (this.draggingObject !== null) this.sceneObjectsChange.emit();
this.draggingObject = null;
});

this.canvas.nativeElement.addEventListener('mouseleave', () => {
if (this.isRunning()) return;

this.draggingObject = null;
});
}

private drawImages() {
public drawImages() {
if (!this.ctx) return;

// Clear the canvas
this.ctx.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height);

// Draw each image on the canvas
for (let imgObj of this.sceneObjects) {
this.ctx.drawImage(imgObj.img, imgObj.x, imgObj.y, imgObj.width, imgObj.height);
}
}

moveTo(x: number, y: number) {
if (this.sceneObjects.length > 0) {
const obj = this.sceneObjects[0];
obj.x = x;
obj.y = y;
this.drawImages();
}
}

moveForward(steps: number) {
const obj = this.sceneObjects[0];
obj.x += steps;
this.drawImages();
}

setDirection(angle: number) {

}
for (let obj of this.sceneObjects) {
this.ctx.save();

turnLeft(angle: number) {

}
if (obj.id === this.selectedObject()) {
this.ctx.shadowColor = 'red';
this.ctx.shadowBlur = 20;
this.ctx.shadowOffsetX = 0;
this.ctx.shadowOffsetY = 0;
}

turnRight(angle: number) {
if (!obj.img) {
const img = new Image();
img.src = obj.imgSrc;
img.onload = () => {
obj.img = img;
this.ctx?.drawImage(obj.img!, obj.x, obj.y, obj.width, obj.height);
}
} else
this.ctx.drawImage(obj.img!, obj.x, obj.y, obj.width, obj.height);

this.ctx.restore();
}
}
}
1 change: 1 addition & 0 deletions src/app/layout/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
teacherStyle="bg-student text-white"
(click)="switchMode()"
data-testid="button"
routerLink=""
/>
</header>
5 changes: 4 additions & 1 deletion src/app/models/activity.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {SceneObject} from './scene-object';

export interface Activity {
id: string,
title: string,
Expand All @@ -7,5 +9,6 @@ export interface Activity {
toolboxInfo: {
BLOCK_LIMITS: { [key: string]: number },
toolboxDefinition: string,
}
},
sceneObjects: SceneObject[]
}
40 changes: 33 additions & 7 deletions src/app/models/scene-object.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
export interface SceneObject {
img: HTMLImageElement;
x: number;
y: number;
rotation: number;
width: number;
height: number;
export class SceneObject {
constructor(
public id: string,
public imgSrc: string,
public x: number,
public y: number,
public rotation: number,
public width: number,
public height: number,
public workspace: string,
public img?: HTMLImageElement,
) {}

moveForward(steps: number) {
this.x += steps;
}

moveTo(x: number, y: number) {
this.x = x;
this.y = y;
}

setDirection(angle: number) {

}

turnLeft(angle: number) {

}

turnRight(angle: number) {

}
}
8 changes: 6 additions & 2 deletions src/app/pages/activity-detail/activity-detail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,19 @@
[workspaceJSON]="activity()!.workspace"
[BLOCKS_LIMITS]="BLOCK_LIMITS"
(openModal)="openBlocksModal()"
(updateLimits)="updateToolboxLimits(workspace)"
(updateLimits)="updateToolboxLimits()"
(saveWorkspace)="saveWorkspace(false)"
/>
<div [ngClass]="['w-1 cursor-ew-resize', modeService.getMode() === 'student' ? 'bg-student' : 'bg-teacher']"></div>
<blearn-scene
class="flex-1"
[sceneObjects]="this.activity()!.sceneObjects"
[isRunning]="isRunning"
[selectedObject]="selectedObject"
(runCode)="onRunCode()"
(stopCode)="isRunning.set(false)"
(sceneObjectsChange)="saveWorkspace(false)"
(objectAdded)="createSceneObject()"
(objectSelected)="selectSceneObject($event)"
/>
</div>

Expand Down
Loading