From 66e549b014e362050fb4dc759e2cdd5ea66ad463 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Mon, 10 Jun 2024 18:03:25 +0200 Subject: [PATCH 01/16] Start moving functionality from the abstract balloon class to the default balloon This is to simplify the abstract class because it now has to much functionallity builtin --- src/balloon.ts | 69 ++++++++++++----------------------------- src/balloons/default.ts | 29 +++++++++++++++++ 2 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/balloon.ts b/src/balloon.ts index 4a7a7923..86fd34f2 100644 --- a/src/balloon.ts +++ b/src/balloon.ts @@ -48,51 +48,12 @@ export const defaultBalloonFolderName = 'default'; export const defaultBalloonResourceLocation = balloonResourceLocation + `${defaultBalloonFolderName}/`; -/** - * Build a balloon element. - * - * This function creates a new balloon element and adds it to the balloon container. - * - * @param element The element to add to the balloon. - * @param props The properties for the balloon element. - * @returns The balloon element. - */ -const buildBalloonElement = ( - element: HTMLDivElement, - props: { - size: number; - positionX: number; - riseDuration: number; - waveDuration: number; - onAnimationend: () => void; - } -) => { - const balloon = document.createElement('div'); - balloon.classList.add('balloon'); - - // Set the balloon's width and height - balloon.style.width = props.size + 'px'; - balloon.style.height = balloon.style.width; - balloon.style.left = `calc(${props.positionX.toString() + 'vw'} - ${props.size / 2}px)`; - balloon.style.animationDuration = props.riseDuration.toString() + 'ms'; - balloon.style.animationTimingFunction = 'linear'; - balloon.style.animationFillMode = 'forwards'; - balloon.style.animationName = 'rise'; - balloon.addEventListener('animationend', props.onAnimationend); - - // Create a second div and apply the swing animation to it - const swingElement = document.createElement('div'); - swingElement.style.animation = `swing ${props.waveDuration}s infinite ease-in-out`; - const waveElement = document.createElement('div'); - waveElement.style.animation = `wave ${props.waveDuration / 2}s infinite ease-in-out alternate`; - // Start wave animation at -3/4 of the swing animation (makes sure the wave has started before the balloon comes on screen) - waveElement.style.animationDelay = `-${(props.waveDuration * 3) / 4}s`; - - balloon.appendChild(swingElement); - swingElement.appendChild(waveElement); - waveElement.appendChild(element); - - return balloon; +export type BuildProps = { + size: number; + positionX: number; + riseDuration: number; + waveDuration: number; + onAnimationend: () => void; }; export default abstract class Balloon { @@ -101,6 +62,17 @@ export default abstract class Balloon { */ public abstract readonly options: BalloonOptions; + /** + * Build a balloon element. + * + * This function creates a new balloon element and adds it to the balloon container. + * + * @param element The element to add to the balloon. + * @param props The properties for the balloon element. + * @returns The balloon element. + */ + public abstract build(props: BuildProps): HTMLDivElement; + /** * The sound element for the pop sound. */ @@ -112,7 +84,7 @@ export default abstract class Balloon { protected readonly balloonImage: HTMLImageElement = document.createElement('img'); - public readonly element: HTMLDivElement; + public readonly element: HTMLDivElement = document.createElement('div'); /** * The duration thresholds for the rise animation. * @@ -189,9 +161,6 @@ export default abstract class Balloon { } constructor() { - // Create the balloon element - this.element = document.createElement('div'); - // Add the balloon image to the balloon element this.element.appendChild(this.balloonImage); @@ -217,7 +186,7 @@ export default abstract class Balloon { // Load the balloon image this.balloonImage.src = this.balloonImageUrl; // Build the balloon element - const balloonElement = buildBalloonElement(this.element, { + const balloonElement = this.buildBalloonElement(this.element, { size: random(50, 75), positionX: random(5, 95), riseDuration: random( diff --git a/src/balloons/default.ts b/src/balloons/default.ts index f4853e6a..8de340d7 100644 --- a/src/balloons/default.ts +++ b/src/balloons/default.ts @@ -3,4 +3,33 @@ import Balloon from '@/balloon'; export default class Default extends Balloon { public static readonly spawn_chance = 0.9; public readonly options = { name: 'default' }; + + public build(e, props) { + const balloon = document.createElement('div'); + balloon.classList.add('balloon'); + + // Set the balloon's width and height + balloon.style.width = props.size + 'px'; + balloon.style.height = balloon.style.width; + balloon.style.left = `calc(${props.positionX.toString() + 'vw'} - ${props.size / 2}px)`; + balloon.style.animationDuration = props.riseDuration.toString() + 'ms'; + balloon.style.animationTimingFunction = 'linear'; + balloon.style.animationFillMode = 'forwards'; + balloon.style.animationName = 'rise'; + balloon.addEventListener('animationend', props.onAnimationend); + + // Create a second div and apply the swing animation to it + const swingElement = document.createElement('div'); + swingElement.style.animation = `swing ${props.waveDuration}s infinite ease-in-out`; + const waveElement = document.createElement('div'); + waveElement.style.animation = `wave ${props.waveDuration / 2}s infinite ease-in-out alternate`; + // Start wave animation at -3/4 of the swing animation (makes sure the wave has started before the balloon comes on screen) + waveElement.style.animationDelay = `-${(props.waveDuration * 3) / 4}s`; + + balloon.appendChild(swingElement); + swingElement.appendChild(waveElement); + waveElement.appendChild(element); + + return balloon; + } } From 488b60b3b39d8420507b8266bdaaf9f2b5bb512d Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Mon, 10 Jun 2024 19:21:33 +0200 Subject: [PATCH 02/16] Run tests in github actions close #219 --- .github/workflows/run-tests.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/run-tests.yaml diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml new file mode 100644 index 00000000..84bfd9e0 --- /dev/null +++ b/.github/workflows/run-tests.yaml @@ -0,0 +1,24 @@ +name: Run tests + +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 21 + - name: Install dependencies + run: npm ci + - name: Run tests + run: | + npm run test From 3aa6f71942253906de7f447d00e043ae31d55756 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Wed, 12 Jun 2024 23:35:58 +0200 Subject: [PATCH 03/16] Refactor TestBalloon class to use build method and remove getRandomDuration --- tests/balloon.test.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/balloon.test.ts b/tests/balloon.test.ts index 4bc45bc3..6716ed1a 100644 --- a/tests/balloon.test.ts +++ b/tests/balloon.test.ts @@ -4,8 +4,8 @@ import { random } from '@/utils'; // Create a concrete subclass of Balloon for testing class TestBalloon extends Balloon { public readonly options = { name: 'test' }; - getRandomDuration() { - return random(10000, 15000); + build() { + return document.createElement('div'); } } @@ -44,13 +44,6 @@ describe('Balloon', () => { expect(balloon.name).toBe('test'); }); - test('getRandomDuration should return a number between 10000 and 15000', () => { - const duration = balloon.getRandomDuration(); - expect(typeof duration).toBe('number'); - expect(duration).toBeGreaterThanOrEqual(10000); - expect(duration).toBeLessThanOrEqual(15000); - }); - test('balloon should be rising after rise is called', () => { expect(balloon.isRising()).toBe(false); balloon.rise(); From 5579b685a5f86c00e56e9a98cd5b85e3dd0f812d Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Wed, 12 Jun 2024 23:37:18 +0200 Subject: [PATCH 04/16] Move more abstract balloon class functionallity to default balloon --- src/balloon.ts | 36 +---------------- src/balloons/confetti.ts | 5 ++- src/balloons/default.ts | 86 +++++++++++++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 46 deletions(-) diff --git a/src/balloon.ts b/src/balloon.ts index 86fd34f2..a3929fec 100644 --- a/src/balloon.ts +++ b/src/balloon.ts @@ -48,14 +48,6 @@ export const defaultBalloonFolderName = 'default'; export const defaultBalloonResourceLocation = balloonResourceLocation + `${defaultBalloonFolderName}/`; -export type BuildProps = { - size: number; - positionX: number; - riseDuration: number; - waveDuration: number; - onAnimationend: () => void; -}; - export default abstract class Balloon { /** * The options for the balloon. @@ -71,7 +63,7 @@ export default abstract class Balloon { * @param props The properties for the balloon element. * @returns The balloon element. */ - public abstract build(props: BuildProps): HTMLDivElement; + public abstract build(): HTMLDivElement; /** * The sound element for the pop sound. @@ -85,18 +77,6 @@ export default abstract class Balloon { document.createElement('img'); public readonly element: HTMLDivElement = document.createElement('div'); - /** - * The duration thresholds for the rise animation. - * - * The first value is the minimum duration and the second value is the maximum duration. - */ - public readonly riseDurationThreshold: [number, number] = [10000, 15000]; - /** - * The duration thresholds for the swing animation. - * - * The first value is the minimum duration and the second value is the maximum duration. - */ - public readonly swingDurationThreshold: [number, number] = [2, 4]; /** * The name of the balloon. @@ -186,19 +166,7 @@ export default abstract class Balloon { // Load the balloon image this.balloonImage.src = this.balloonImageUrl; // Build the balloon element - const balloonElement = this.buildBalloonElement(this.element, { - size: random(50, 75), - positionX: random(5, 95), - riseDuration: random( - this.riseDurationThreshold[0], - this.riseDurationThreshold[1] - ), - waveDuration: random( - this.swingDurationThreshold[0], - this.swingDurationThreshold[1] - ), - onAnimationend: this.remove.bind(this), - }); + const balloonElement = this.build(); // Add the balloon to the container getBalloonContainer().appendChild(balloonElement); } diff --git a/src/balloons/confetti.ts b/src/balloons/confetti.ts index 5c976ffe..60f56b41 100644 --- a/src/balloons/confetti.ts +++ b/src/balloons/confetti.ts @@ -1,8 +1,9 @@ import Balloon, { balloonResourceLocation } from '@/balloon'; import { importStylesheet, random } from '@/utils'; +import Default from './default'; -export default class Confetti extends Balloon { - public static readonly spawn_chance = 0.1; +export default class Confetti extends Default { + public static readonly spawn_chance: number = 0.1; public readonly options = { name: 'confetti' }; private readonly mask = document.createElement('img'); diff --git a/src/balloons/default.ts b/src/balloons/default.ts index 8de340d7..8992ccdd 100644 --- a/src/balloons/default.ts +++ b/src/balloons/default.ts @@ -1,34 +1,100 @@ import Balloon from '@/balloon'; +import { random } from '@/utils'; + +export type BuildProps = { + size: number; + positionX: number; + riseDuration: number; + waveDuration: number; + onAnimationend: () => void; +}; + +/** + * The options for the balloon. + */ +export type BalloonOptions = { + /** + * The name of the balloon. It should be the same as the name of the class in lower case. + * + * This is used to determine the folder name for the balloon resources. + */ + name: string; + /** + * The URL of the image to display on the balloon. + * If not provided, the default image will be used. + */ + imageUrl?: string; + /** + * The URL of the sound to play when the balloon is popped. + * If not provided, the default sound will be used. + */ + popSoundUrl?: string; +}; + +export type DefaultBalloonOptions = Required; + +/** + * The default options for the balloon. These are used when the options are not provided. + */ +const defaultBalloonOptions: DefaultBalloonOptions = { + name: 'default', + imageUrl: '/icon.png', + popSoundUrl: '/pop.mp3', +}; export default class Default extends Balloon { - public static readonly spawn_chance = 0.9; + public static readonly spawn_chance: number = 0.9; public readonly options = { name: 'default' }; - public build(e, props) { + /** + * The duration thresholds for the rise animation. + * + * The first value is the minimum duration and the second value is the maximum duration. + */ + public readonly riseDurationThreshold: [number, number] = [10000, 15000]; + /** + * The duration thresholds for the swing animation. + * + * The first value is the minimum duration and the second value is the maximum duration. + */ + public readonly swingDurationThreshold: [number, number] = [2, 4]; + + public build() { + const size = random(50, 75); + const positionX = random(5, 95); + const riseDuration = random( + this.riseDurationThreshold[0], + this.riseDurationThreshold[1] + ); + const waveDuration = random( + this.swingDurationThreshold[0], + this.swingDurationThreshold[1] + ); + const balloon = document.createElement('div'); balloon.classList.add('balloon'); // Set the balloon's width and height - balloon.style.width = props.size + 'px'; + balloon.style.width = size + 'px'; balloon.style.height = balloon.style.width; - balloon.style.left = `calc(${props.positionX.toString() + 'vw'} - ${props.size / 2}px)`; - balloon.style.animationDuration = props.riseDuration.toString() + 'ms'; + balloon.style.left = `calc(${positionX.toString() + 'vw'} - ${size / 2}px)`; + balloon.style.animationDuration = riseDuration.toString() + 'ms'; balloon.style.animationTimingFunction = 'linear'; balloon.style.animationFillMode = 'forwards'; balloon.style.animationName = 'rise'; - balloon.addEventListener('animationend', props.onAnimationend); + balloon.addEventListener('animationend', this.remove.bind(this)); // Create a second div and apply the swing animation to it const swingElement = document.createElement('div'); - swingElement.style.animation = `swing ${props.waveDuration}s infinite ease-in-out`; + swingElement.style.animation = `swing ${waveDuration}s infinite ease-in-out`; const waveElement = document.createElement('div'); - waveElement.style.animation = `wave ${props.waveDuration / 2}s infinite ease-in-out alternate`; + waveElement.style.animation = `wave ${waveDuration / 2}s infinite ease-in-out alternate`; // Start wave animation at -3/4 of the swing animation (makes sure the wave has started before the balloon comes on screen) - waveElement.style.animationDelay = `-${(props.waveDuration * 3) / 4}s`; + waveElement.style.animationDelay = `-${(waveDuration * 3) / 4}s`; balloon.appendChild(swingElement); swingElement.appendChild(waveElement); - waveElement.appendChild(element); + waveElement.appendChild(this.element); return balloon; } From 5b5bb919033c0eecd02f5d2b7912d1b5ed96b336 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Thu, 13 Jun 2024 22:50:41 +0200 Subject: [PATCH 05/16] Move the rest from abstract class to default class --- src/balloon.ts | 146 ++-------------------------------------- src/balloons/default.ts | 75 ++++++++++++++++----- 2 files changed, 66 insertions(+), 155 deletions(-) diff --git a/src/balloon.ts b/src/balloon.ts index a3929fec..7a0684ce 100644 --- a/src/balloon.ts +++ b/src/balloon.ts @@ -2,39 +2,6 @@ import browser from 'webextension-polyfill'; import storage from '@/managers/storage'; import { getBalloonContainer, random, sendMessage } from '@/utils'; -/** - * The options for the balloon. - */ -export type BalloonOptions = { - /** - * The name of the balloon. It should be the same as the name of the class in lower case. - * - * This is used to determine the folder name for the balloon resources. - */ - name: string; - /** - * The URL of the image to display on the balloon. - * If not provided, the default image will be used. - */ - imageUrl?: string; - /** - * The URL of the sound to play when the balloon is popped. - * If not provided, the default sound will be used. - */ - popSoundUrl?: string; -}; - -export type DefaultBalloonOptions = Required; - -/** - * The default options for the balloon. These are used when the options are not provided. - */ -const defaultBalloonOptions: DefaultBalloonOptions = { - name: 'default', - imageUrl: '/icon.png', - popSoundUrl: '/pop.mp3', -}; - /** * The location of the balloon resources. (`resources/balloons/`) */ @@ -49,10 +16,7 @@ export const defaultBalloonResourceLocation = balloonResourceLocation + `${defaultBalloonFolderName}/`; export default abstract class Balloon { - /** - * The options for the balloon. - */ - public abstract readonly options: BalloonOptions; + public abstract readonly name: string; /** * Build a balloon element. @@ -65,94 +29,18 @@ export default abstract class Balloon { */ public abstract build(): HTMLDivElement; - /** - * The sound element for the pop sound. - */ - private readonly _popSound: HTMLAudioElement = new Audio(); - - /** - * The image element for the balloon image. - */ - protected readonly balloonImage: HTMLImageElement = - document.createElement('img'); - public readonly element: HTMLDivElement = document.createElement('div'); - /** - * The name of the balloon. - * - * Should be the same as the name of the class in lower case. - * - * This is used to determine the folder name for the balloon resources. - */ - public get name(): string { - return this.options.name; - } - - /** - * The audio element for the pop sound. - */ - public get popSound(): HTMLAudioElement { - if (!this._popSound.src) { - this._popSound.src = this.popSoundUrl; - } - return this._popSound; - } - - /** - * The URL of the balloon image. - */ - public get balloonImageUrl(): string { - return ( - balloonResourceLocation + - (this.options.imageUrl ? this.options.name : defaultBalloonOptions.name) + - (this.options.imageUrl ?? defaultBalloonOptions.imageUrl) - ); - } - - /** - * The URL of the pop sound. - */ - public get popSoundUrl(): string { - return ( - balloonResourceLocation + - (this.options.popSoundUrl - ? this.options.name - : defaultBalloonOptions.name) + - (this.options.popSoundUrl ?? defaultBalloonOptions.popSoundUrl) - ); - } - - /** - * The top element of the balloon. Which is a direct child of the balloon container. - */ - public get topElement(): HTMLDivElement { - let element = this.element; - while (!element.classList.contains('balloon')) { - if ( - !element.parentElement || - !(element.parentElement instanceof HTMLDivElement) || - element.parentElement === getBalloonContainer() - ) - return element; - element = element.parentElement; - } - return element; - } - constructor() { - // Add the balloon image to the balloon element - this.element.appendChild(this.balloonImage); - // Add an event listener to the balloon - this.element.addEventListener('click', this._pop.bind(this)); + this.element.addEventListener('click', this.pop.bind(this)); } /** * @returns Whether the balloon is rising. */ public isRising(): boolean { - return this.topElement.style.animationName === 'rise'; + return this.element.style.animationName === 'rise'; } /** @@ -161,10 +49,6 @@ export default abstract class Balloon { * This will create a new balloon element and add it to the balloon container. */ public rise(): void { - // Load the pop sound - const _ = this.popSound; - // Load the balloon image - this.balloonImage.src = this.balloonImageUrl; // Build the balloon element const balloonElement = this.build(); // Add the balloon to the container @@ -176,38 +60,22 @@ export default abstract class Balloon { */ public remove(): void { // loop until the parent node has 'balloon' class - this.topElement.remove(); - this.topElement.style.animationName = 'none'; + this.element.remove(); + this.element.style.animationName = 'none'; } /** * Pop the balloon. * - * This will remove the balloon, play the pop sound and send a message to increment the count. + * This will be called after the balloon is removed and the pop sound is played. * * @param event The mouse event that triggered the pop. */ - private async _pop(event: MouseEvent): Promise { + public pop(event?: MouseEvent): void | Promise { // Remove the balloon this.remove(); // Send message with the new count sendMessage({ action: 'incrementCount' }); - - // Set volume - this.popSound.volume = (await storage.sync.get('config')).popVolume / 100; - // Play the pop sound - this.popSound.play(); - - this.pop(event); } - - /** - * Pop the balloon. - * - * This will be called after the balloon is removed and the pop sound is played. - * - * @param event The mouse event that triggered the pop. - */ - public pop(event?: MouseEvent): void | Promise {} } diff --git a/src/balloons/default.ts b/src/balloons/default.ts index 8992ccdd..f94e5664 100644 --- a/src/balloons/default.ts +++ b/src/balloons/default.ts @@ -1,4 +1,5 @@ -import Balloon from '@/balloon'; +import Balloon, { balloonResourceLocation } from '@/balloon'; +import storage from '@/managers/storage'; import { random } from '@/utils'; export type BuildProps = { @@ -18,33 +19,27 @@ export type BalloonOptions = { * * This is used to determine the folder name for the balloon resources. */ - name: string; + dir_name: string; /** * The URL of the image to display on the balloon. * If not provided, the default image will be used. */ - imageUrl?: string; + imageUrl: string; /** * The URL of the sound to play when the balloon is popped. * If not provided, the default sound will be used. */ - popSoundUrl?: string; -}; - -export type DefaultBalloonOptions = Required; - -/** - * The default options for the balloon. These are used when the options are not provided. - */ -const defaultBalloonOptions: DefaultBalloonOptions = { - name: 'default', - imageUrl: '/icon.png', - popSoundUrl: '/pop.mp3', + popSoundUrl: string; }; export default class Default extends Balloon { public static readonly spawn_chance: number = 0.9; - public readonly options = { name: 'default' }; + public readonly name: string = 'default'; + public readonly options: BalloonOptions = { + dir_name: this.name, + imageUrl: '/icon.png', + popSoundUrl: '/pop.mp3', + }; /** * The duration thresholds for the rise animation. @@ -59,6 +54,46 @@ export default class Default extends Balloon { */ public readonly swingDurationThreshold: [number, number] = [2, 4]; + /** + * The image element for the balloon image. + */ + protected readonly balloonImage: HTMLImageElement = + document.createElement('img'); + + /** + * The sound element for the pop sound. + */ + private readonly popSound: HTMLAudioElement = new Audio(); + + /** + * The URL of the balloon image. + */ + public get balloonImageUrl(): string { + return ( + balloonResourceLocation + this.options.dir_name + this.options.imageUrl + ); + } + + /** + * The URL of the pop sound. + */ + public get popSoundUrl(): string { + return ( + balloonResourceLocation + this.options.dir_name + this.options.popSoundUrl + ); + } + + constructor() { + super(); + // Load the pop sound + this.popSound.src = this.popSoundUrl; + // Load the balloon image + this.balloonImage.src = this.balloonImageUrl; + + // Add the balloon image to the balloon element + this.element.appendChild(this.balloonImage); + } + public build() { const size = random(50, 75); const positionX = random(5, 95); @@ -98,4 +133,12 @@ export default class Default extends Balloon { return balloon; } + + public async pop(event: MouseEvent) { + super.pop(); + // Set volume + this.popSound.volume = (await storage.sync.get('config')).popVolume / 100; + // Play the pop sound + this.popSound.play(); + } } From 74f47572ad172102fe53aab1fddeb209b93ffd79 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Thu, 13 Jun 2024 22:50:55 +0200 Subject: [PATCH 06/16] Update confetti to match default --- src/balloons/confetti.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/balloons/confetti.ts b/src/balloons/confetti.ts index 60f56b41..b77a0eab 100644 --- a/src/balloons/confetti.ts +++ b/src/balloons/confetti.ts @@ -4,7 +4,7 @@ import Default from './default'; export default class Confetti extends Default { public static readonly spawn_chance: number = 0.1; - public readonly options = { name: 'confetti' }; + public readonly name = 'confetti'; private readonly mask = document.createElement('img'); @@ -24,7 +24,8 @@ export default class Confetti extends Default { this.mask.style.transform = `rotate(${Math.random() * 360}deg)`; } - public pop(event?: MouseEvent) { + public async pop(event: MouseEvent) { + super.pop(event); // Get the click position const x = event?.clientX || window.innerWidth / 2; const y = event?.clientY || window.innerHeight / 2; From 0d041470f52b43a698eafd48db977c33c8ec4740 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Thu, 13 Jun 2024 22:51:06 +0200 Subject: [PATCH 07/16] Update balloon tests --- tests/balloon.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/balloon.test.ts b/tests/balloon.test.ts index 6716ed1a..6220c890 100644 --- a/tests/balloon.test.ts +++ b/tests/balloon.test.ts @@ -3,7 +3,7 @@ import { random } from '@/utils'; // Create a concrete subclass of Balloon for testing class TestBalloon extends Balloon { - public readonly options = { name: 'test' }; + public readonly name = 'test'; build() { return document.createElement('div'); } From bf79528fb9cbce7a70ba006059cce05fb39634b9 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Thu, 13 Jun 2024 23:01:33 +0200 Subject: [PATCH 08/16] Build the balloon with the intended html element --- src/balloon.ts | 6 +++--- src/balloons/default.ts | 30 ++++++++++++------------------ 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/balloon.ts b/src/balloon.ts index 7a0684ce..29ed5c34 100644 --- a/src/balloon.ts +++ b/src/balloon.ts @@ -27,7 +27,7 @@ export default abstract class Balloon { * @param props The properties for the balloon element. * @returns The balloon element. */ - public abstract build(): HTMLDivElement; + public abstract build(): void; public readonly element: HTMLDivElement = document.createElement('div'); @@ -50,9 +50,9 @@ export default abstract class Balloon { */ public rise(): void { // Build the balloon element - const balloonElement = this.build(); + this.build(); // Add the balloon to the container - getBalloonContainer().appendChild(balloonElement); + getBalloonContainer().appendChild(this.element); } /** diff --git a/src/balloons/default.ts b/src/balloons/default.ts index f94e5664..33943cbb 100644 --- a/src/balloons/default.ts +++ b/src/balloons/default.ts @@ -89,9 +89,6 @@ export default class Default extends Balloon { this.popSound.src = this.popSoundUrl; // Load the balloon image this.balloonImage.src = this.balloonImageUrl; - - // Add the balloon image to the balloon element - this.element.appendChild(this.balloonImage); } public build() { @@ -106,18 +103,17 @@ export default class Default extends Balloon { this.swingDurationThreshold[1] ); - const balloon = document.createElement('div'); - balloon.classList.add('balloon'); + this.element.classList.add('balloon'); // Set the balloon's width and height - balloon.style.width = size + 'px'; - balloon.style.height = balloon.style.width; - balloon.style.left = `calc(${positionX.toString() + 'vw'} - ${size / 2}px)`; - balloon.style.animationDuration = riseDuration.toString() + 'ms'; - balloon.style.animationTimingFunction = 'linear'; - balloon.style.animationFillMode = 'forwards'; - balloon.style.animationName = 'rise'; - balloon.addEventListener('animationend', this.remove.bind(this)); + this.element.style.width = size + 'px'; + this.element.style.height = this.element.style.width; + this.element.style.left = `calc(${positionX.toString() + 'vw'} - ${size / 2}px)`; + this.element.style.animationDuration = riseDuration.toString() + 'ms'; + this.element.style.animationTimingFunction = 'linear'; + this.element.style.animationFillMode = 'forwards'; + this.element.style.animationName = 'rise'; + this.element.addEventListener('animationend', this.remove.bind(this)); // Create a second div and apply the swing animation to it const swingElement = document.createElement('div'); @@ -127,11 +123,9 @@ export default class Default extends Balloon { // Start wave animation at -3/4 of the swing animation (makes sure the wave has started before the balloon comes on screen) waveElement.style.animationDelay = `-${(waveDuration * 3) / 4}s`; - balloon.appendChild(swingElement); - swingElement.appendChild(waveElement); - waveElement.appendChild(this.element); - - return balloon; + waveElement.appendChild(swingElement); + swingElement.appendChild(this.balloonImage); + this.element.appendChild(waveElement); } public async pop(event: MouseEvent) { From c3f44e02522f2c546fb71c4ecfea300743df89e2 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Thu, 13 Jun 2024 23:08:10 +0200 Subject: [PATCH 09/16] Re-order the animation order --- src/balloons/default.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/balloons/default.ts b/src/balloons/default.ts index 33943cbb..a8c29bab 100644 --- a/src/balloons/default.ts +++ b/src/balloons/default.ts @@ -123,9 +123,9 @@ export default class Default extends Balloon { // Start wave animation at -3/4 of the swing animation (makes sure the wave has started before the balloon comes on screen) waveElement.style.animationDelay = `-${(waveDuration * 3) / 4}s`; - waveElement.appendChild(swingElement); - swingElement.appendChild(this.balloonImage); - this.element.appendChild(waveElement); + swingElement.appendChild(waveElement); + waveElement.appendChild(this.balloonImage); + this.element.appendChild(swingElement); } public async pop(event: MouseEvent) { From 97034a6e71c445b7c283d6d8a8a4c023f1495d0f Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Thu, 13 Jun 2024 23:08:44 +0200 Subject: [PATCH 10/16] Add the confetti mask in the build method --- src/balloons/confetti.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/balloons/confetti.ts b/src/balloons/confetti.ts index b77a0eab..9c39d796 100644 --- a/src/balloons/confetti.ts +++ b/src/balloons/confetti.ts @@ -14,8 +14,11 @@ export default class Confetti extends Default { 'confetti-styles', balloonResourceLocation + 'confetti/confetti.css' ); + } - this.element.appendChild(this.mask); + public build(): void { + super.build(); + this.element.firstChild?.firstChild?.appendChild(this.mask); this.mask.src = balloonResourceLocation + this.name + '/mask.png'; this.mask.style.position = 'absolute'; this.mask.style.top = '-10px'; From 19ca36599f1e992715a683560576cd6bdc60bf7b Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Thu, 13 Jun 2024 23:14:14 +0200 Subject: [PATCH 11/16] Remove unused imports --- src/balloon.ts | 3 +-- src/balloons/confetti.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/balloon.ts b/src/balloon.ts index 29ed5c34..d9b49bdb 100644 --- a/src/balloon.ts +++ b/src/balloon.ts @@ -1,6 +1,5 @@ import browser from 'webextension-polyfill'; -import storage from '@/managers/storage'; -import { getBalloonContainer, random, sendMessage } from '@/utils'; +import { getBalloonContainer, sendMessage } from '@/utils'; /** * The location of the balloon resources. (`resources/balloons/`) diff --git a/src/balloons/confetti.ts b/src/balloons/confetti.ts index 9c39d796..4f6c6cc8 100644 --- a/src/balloons/confetti.ts +++ b/src/balloons/confetti.ts @@ -1,4 +1,4 @@ -import Balloon, { balloonResourceLocation } from '@/balloon'; +import { balloonResourceLocation } from '@/balloon'; import { importStylesheet, random } from '@/utils'; import Default from './default'; From d1121f3346162fa28eb31a762c327f8815ed59c2 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Sat, 15 Jun 2024 10:45:43 +0200 Subject: [PATCH 12/16] Remove non usefull test --- tests/balloon.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/balloon.test.ts b/tests/balloon.test.ts index 6220c890..06e55d2f 100644 --- a/tests/balloon.test.ts +++ b/tests/balloon.test.ts @@ -1,5 +1,4 @@ import Balloon from '@/balloon'; -import { random } from '@/utils'; // Create a concrete subclass of Balloon for testing class TestBalloon extends Balloon { @@ -44,12 +43,6 @@ describe('Balloon', () => { expect(balloon.name).toBe('test'); }); - test('balloon should be rising after rise is called', () => { - expect(balloon.isRising()).toBe(false); - balloon.rise(); - expect(balloon.isRising()).toBe(true); - }); - test('balloon should not be rising after remove is called', () => { balloon.rise(); balloon.remove(); From af06a2778d96fe3120765d053c2c1a8ef9781cfa Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Sat, 15 Jun 2024 12:57:28 +0200 Subject: [PATCH 13/16] Update protection level for Default balloon class --- src/balloons/default.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/balloons/default.ts b/src/balloons/default.ts index a8c29bab..1b4e2822 100644 --- a/src/balloons/default.ts +++ b/src/balloons/default.ts @@ -57,13 +57,13 @@ export default class Default extends Balloon { /** * The image element for the balloon image. */ - protected readonly balloonImage: HTMLImageElement = + public readonly balloonImage: HTMLImageElement = document.createElement('img'); /** * The sound element for the pop sound. */ - private readonly popSound: HTMLAudioElement = new Audio(); + public readonly popSound: HTMLAudioElement = new Audio(); /** * The URL of the balloon image. From 71140474497766e965ee59da27d76d53ce085277 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Sat, 15 Jun 2024 15:01:55 +0200 Subject: [PATCH 14/16] Make username optional #225 --- src/background/background.ts | 2 +- src/const.ts | 2 +- src/popup/components/forms/UpdateUser.tsx | 7 +++++-- src/remote.ts | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/background/background.ts b/src/background/background.ts index d50c442e..09ccf05c 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -78,7 +78,7 @@ const updateBadgeColors = () => { let localUser = await storage.sync.get('user'); // If the user is not in the local storage, get a new user from the remote if (!localUser) { - const usr = await remote.NewUser('Anonymous'); + const usr = await remote.NewUser(); await storage.sync.set('token', usr.token); localUser = usr; } diff --git a/src/const.ts b/src/const.ts index ab3253fd..9d640182 100644 --- a/src/const.ts +++ b/src/const.ts @@ -31,7 +31,7 @@ export const initalConfig: _initialConfig = { export type User = { id: string; - username: string; + username?: string; email?: string; count: number; updatedAt: string; diff --git a/src/popup/components/forms/UpdateUser.tsx b/src/popup/components/forms/UpdateUser.tsx index ec0f3edd..b5650091 100644 --- a/src/popup/components/forms/UpdateUser.tsx +++ b/src/popup/components/forms/UpdateUser.tsx @@ -47,7 +47,7 @@ export default () => { const loadUser = async () => { const storedUser = await storage.sync.get('user'); setUser(storedUser); - form.setValue('username', storedUser.username); + form.setValue('username', storedUser.username || ''); form.setValue('email', storedUser.email || ''); }; @@ -56,7 +56,10 @@ export default () => { const onSubmit = async ({ username, email }: z.infer) => { if (username === user?.username && email === user?.email) return; - const newUser = await remote.putUser({ username, email }); + const newUser = await remote.putUser({ + username: username !== '' ? username : undefined, + email: email !== '' ? email : undefined, + }); setUser(newUser); await storage.sync.set('user', newUser); }; diff --git a/src/remote.ts b/src/remote.ts index a3b3464a..1cea0946 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -93,7 +93,7 @@ class BackendAPI { ); } - public async NewUser(username: string, email?: string) { + public async NewUser(username?: string, email?: string) { return await this.request< Prettify<{ token: string } & RemoteResponse['user']> >('POST', '/user/new', { From 0a6f3532bf8311934f6131e20233ef470f4d062a Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Sun, 16 Jun 2024 18:06:20 +0200 Subject: [PATCH 15/16] Update outdated deps --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index b357abd8..5781cdb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3063,11 +3063,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -4401,9 +4401,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, From ae9e813e23d5737f974425bea40f1d06aa7c3e00 Mon Sep 17 00:00:00 2001 From: SimonStnn Date: Sun, 16 Jun 2024 18:06:37 +0200 Subject: [PATCH 16/16] Bump package version to 1.10.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5781cdb4..1069ab69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pop-a-loon", - "version": "1.10.2", + "version": "1.10.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pop-a-loon", - "version": "1.10.2", + "version": "1.10.3", "license": "Apache-2.0", "dependencies": { "@hookform/resolvers": "^3.3.4", diff --git a/package.json b/package.json index 6d45139a..6baf45f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pop-a-loon", - "version": "1.10.2", + "version": "1.10.3", "description": "The new rising trend (literally) that changes the browser game completely.", "private": true, "scripts": {