Skip to content

Commit

Permalink
Merge branch 'dragActivity8'
Browse files Browse the repository at this point in the history
Interactive review with Andrew
  • Loading branch information
JohnThomson committed Jul 24, 2024
2 parents 6b970fb + 0ab92ea commit 056b865
Show file tree
Hide file tree
Showing 11 changed files with 2,544 additions and 1,102 deletions.
8 changes: 5 additions & 3 deletions src/activities/ActivityContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,13 @@ export class ActivityContext {
// NB: if this stops working in storybook; the file should be found because the package.json
// script that starts storybook has a "--static-dir" option that should include the folder
// containing the standard activity sounds.
this.playSound(rightAnswer);
// require on an mp3 gives us some sort of module object where the url of the sound is its 'default'
this.playSound(rightAnswer.default);
}

public playWrong() {
this.playSound(wrongAnswer);
// require on an mp3 gives us some sort of module object where the url of the sound is its 'default'
this.playSound(wrongAnswer.default);
}

private getPagePlayer(): any {
Expand All @@ -96,7 +98,7 @@ export class ActivityContext {
return player;
}

public playSound(url) {
public playSound(url: string) {
const player = this.getPagePlayer();
player.setAttribute("src", url);
player.play();
Expand Down
46 changes: 46 additions & 0 deletions src/activities/activityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ActivityContext } from "./ActivityContext";
const iframeModule = require("./iframeActivity.ts");
const simpleDomChoiceActivityModule = require("./domActivities/SimpleDomChoice.ts");
const simpleCheckboxQuizModule = require("./domActivities/SimpleCheckboxQuiz.ts");
const dragToDestinationModule = require("./dragActivities/DragToDestination.ts");

// This is the module that the activity has to implement (the file must export these functions)
export interface IActivityModule {
Expand All @@ -23,6 +24,10 @@ export interface IActivityObject {
// This is acting on the real DOM, so this is the time to set up event handlers, etc.
showingPage: (context: ActivityContext) => void;

// This is called in place of the normal code that plays sound and animations when the page first appears,
// if the activity's requirements specify soundManagement: true.
doInitialSoundAndAnimation?: (context: ActivityContext) => void;

stop: () => void;
}
// Constructing stuff from interfaces has problems with typescript at the moment.
Expand All @@ -35,6 +40,10 @@ export interface IActivityRequirements {
dragging?: boolean;
clicking?: boolean;
typing?: boolean;
// suppress normal sound (and music, and animation)
// If this is true, the activity should implement doInitialSoundAndAnimation
// if anything should autoplay when the page appears.
soundManagement?: boolean;
}

// This is the object (implemented by us, not the activity) that represents our own
Expand All @@ -60,6 +69,29 @@ export class ActivityManager {
this.builtInActivities[
simpleCheckboxQuizModule.dataActivityID
] = simpleCheckboxQuizModule as IActivityModule;

// Review: currently these all use the same module. A lot of stuff is shared, all the way down to the
// prepareActivity() function in dragActivityRuntime. But a good many specialized TOP types are
// specific to one of them and not needed for the others. It may be helpful to tease things
// apart more, for example, three separate implementations of IActivityModule and PrepareActivity
// which call common code for the setup tasks common to all three. OTOH, in some ways it is simpler
// to have it all in one place, and just do the appropriate initialization based on what kind of
// draggables we find.
this.builtInActivities[
"drag-to-destination" // not currently used
] = dragToDestinationModule as IActivityModule;
this.builtInActivities[
"drag-letter-to-target"
] = dragToDestinationModule as IActivityModule;
this.builtInActivities[
"drag-image-to-target"
] = dragToDestinationModule as IActivityModule;
this.builtInActivities[
"drag-sort-sentence"
] = dragToDestinationModule as IActivityModule;
this.builtInActivities[
"word-chooser-slider" // not used yet
] = dragToDestinationModule as IActivityModule;
}
public getActivityAbsorbsDragging(): boolean {
return (
Expand All @@ -78,6 +110,12 @@ export class ActivityManager {
!!this.currentActivity && !!this.currentActivity.requirements.typing
);
}
public getActivityManagesSound(): boolean {
return (
!!this.currentActivity &&
!!this.currentActivity.requirements.soundManagement
);
}
private currentActivity: IActivityInformation | undefined;
private loadedActivityScripts: {
[name: string]: IActivityInformation;
Expand Down Expand Up @@ -199,6 +237,14 @@ export class ActivityManager {
}
}

public doInitialSoundAndAnimation() {
if (this.currentActivity && this.currentActivity.runningObject) {
this.currentActivity.runningObject.doInitialSoundAndAnimation?.(
this.currentActivity.context!
);
}
}

// Showing a new page, so stop any previous activity and start any new one that might be on the new page.
// returns true if this is a page where we are going to have state in the DOM so that the
// container needs to be careful not to get rid of it to save memory.
Expand Down
91 changes: 91 additions & 0 deletions src/activities/dragActivities/DragToDestination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { ActivityContext } from "../ActivityContext";
import { IActivityObject, IActivityRequirements } from "../activityManager";
import {
playInitialElements,
prepareActivity,
undoPrepareActivity
} from "../../dragActivityRuntime";
// tslint:disable-next-line: no-submodule-imports
/* Not using. See comment below:
const activityCss = require("!!raw-loader!./multipleChoiceDomActivity.css")
.default;*/

// This class is basically an adapter that implements IActivityObject so that activities that
// are created by Bloom's DragActivityTool (the 2024 Bloom Games) can connect to the
// dragActivityRuntime.ts functions that actually do the work of the activity (and are shared with
// Bloom desktop's Play mode).)

// Note that you won't find any code using this directly. Instead,
// it gets used by the ActivityManager as the default export of this module.
export default class DragToDestinationActivity implements IActivityObject {
private activityContext: ActivityContext;
// When a page that has this activity becomes the selected one, the bloom-player calls this.
// We need to connect any listeners, start animation, etc. Here,
// we are using a javascript class to make sure that we get a fresh start,
// which is important because the user could be either
// coming back to this page, or going to another instance of this activity
// in a subsequent page.
public constructor(public pageElement: HTMLElement) {}

public showingPage(activityContext: ActivityContext) {
this.activityContext = activityContext;
this.prepareToDisplayActivityEachTime(activityContext);
}

public doInitialSoundAndAnimation(activityContext: ActivityContext) {
playInitialElements(activityContext.pageElement);
}

// Do just those things that we only want to do once per read of the book.
// In the current implementation of activityManager, this is operating on a copy of the page html,
// NOT the real DOM the user will eventually interact with.
public initializePageHtml(activityContext: ActivityContext) {}

// The context removes event listeners each time the page is shown, so we have to put them back.
private prepareToDisplayActivityEachTime(activityContext: ActivityContext) {
this.activityContext = activityContext;
// This class is added one layer outside the page body. This is an element that is a wrapper
// for our scoped styles...the furthest out element we can put classes on and have them work
// properly with scoped styles. It's a good place to put classes that affect the state of everything
// in the page. This class indicates in Bloom Desktop that the page is in play mode.
// In Bloom Player, it always is. This helps make a common stylesheet work consistently.
// Bloom Player may also add drag-activity-correct or drag-activity-wrong to this element,
// after checking an answer, or drag-activity-solution when showing the answer.
activityContext.pageElement.parentElement?.classList.add(
"drag-activity-play"
);
prepareActivity(activityContext.pageElement, next => {
// Move to the next or previous page. None of our current bloom game activities use this, but it's available
// if we create an activity with built-in next/previous page buttons.
if (next) {
activityContext.navigateToNextPage();
} else {
activityContext.navigateToPreviousPage();
}
});
}

// When our page is not the selected one, the bloom-player calls this.
// It will also tell our context to stop, which will disconnect the listeners we registered with it
public stop() {
if (this.activityContext) {
undoPrepareActivity(this.activityContext.pageElement);
this.activityContext.pageElement.parentElement?.classList.remove(
"drag-activity-play",
"drag-activity-start", // I don't think Bloom Player will ever add this, but just in case.
"drag-activity-correct",
"drag-activity-wrong",
"drag-activity-solution"
);
}
}
}

export function activityRequirements(): IActivityRequirements {
return {
dragging: true, // this activity is all about dragging things around, we don't want dragging to change pages
clicking: true, // not sure we need this, but can we actually support dragging without supporting clicking?
typing: false,
soundManagement: true // many sounds played only after specific events.
};
}
Loading

0 comments on commit 056b865

Please sign in to comment.