Skip to content

Commit

Permalink
Merge pull request #77 from pawcoding/feat/confetti
Browse files Browse the repository at this point in the history
Confetti 🎉 🎊
  • Loading branch information
pawcoding authored Aug 15, 2024
2 parents 90e1f23 + c36656d commit 1858415
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 249 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@ng-icons/simple-icons": "^26.5.0",
"@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0",
"canvas-confetti": "^1.9.3",
"ngx-matomo-client": "~6.2.0",
"rxjs": "~7.8.1",
"string-to-unicode-variant": "^1.0.9",
Expand Down Expand Up @@ -77,6 +78,7 @@
"@storybook/blocks": "^8.2.9",
"@storybook/test": "^8.2.9",
"@tailwindcss/forms": "~0.5.7",
"@types/canvas-confetti": "^1.6.4",
"@types/jasmine": "~5.1.4",
"@types/node": "^20.14.15",
"@typescript-eslint/eslint-plugin": "^7.18.0",
Expand Down
263 changes: 18 additions & 245 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/app/export/export-modal.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { AnalyticsService, AnalyticsServiceMock } from '../shared/data-access/analytics.service';
import { ConfettiService, ConfettiServiceMock } from '../shared/data-access/confetti.service';
import { ExportService, ExportServiceMock } from '../shared/data-access/export.service';
import { ToastService, ToastServiceMock } from '../shared/data-access/toast.service';
import { Color, Palette, Shade } from '../shared/model';
Expand Down Expand Up @@ -36,6 +37,10 @@ describe('ExportModalComponent', () => {
{
provide: AnalyticsService,
useClass: AnalyticsServiceMock
},
{
provide: ConfettiService,
useClass: ConfettiServiceMock
}
]
}).compileComponents();
Expand Down
4 changes: 3 additions & 1 deletion src/app/export/export-modal.component.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
import { Meta, applicationConfig } from '@storybook/angular';
import { Tailwind } from '../shared/constants/tailwind-colors';
import { AnalyticsService, AnalyticsServiceMock } from '../shared/data-access/analytics.service';
import { ConfettiService, ConfettiServiceMock } from '../shared/data-access/confetti.service';
import { ExportService, ExportServiceMock } from '../shared/data-access/export.service';
import { ToastService, ToastServiceMock } from '../shared/data-access/toast.service';
import { createStory } from '../shared/utils/storybook';
Expand All @@ -23,7 +24,8 @@ const meta: Meta<ExportModalComponent> = {
},
{ provide: ToastService, useClass: ToastServiceMock },
{ provide: ExportService, useClass: ExportServiceMock },
{ provide: AnalyticsService, useClass: AnalyticsServiceMock }
{ provide: AnalyticsService, useClass: AnalyticsServiceMock },
{ provide: ConfettiService, useClass: ConfettiServiceMock }
]
})
]
Expand Down
10 changes: 10 additions & 0 deletions src/app/export/export-modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { heroArrowLeftMini, heroXMarkMini } from '@ng-icons/heroicons/mini';
import { TranslateModule } from '@ngx-translate/core';
import { ExportFormat } from '../shared/constants/export-format';
import { AnalyticsService } from '../shared/data-access/analytics.service';
import { ConfettiService } from '../shared/data-access/confetti.service';
import { ExportService } from '../shared/data-access/export.service';
import { ToastService } from '../shared/data-access/toast.service';
import { TrackingEventAction, TrackingEventCategory } from '../shared/enums/tracking-event';
Expand Down Expand Up @@ -43,6 +44,7 @@ export class ExportModalComponent {
private readonly _toastService = inject(ToastService);
private readonly _exportService = inject(ExportService);
private readonly _analyticsService = inject(AnalyticsService);
private readonly _confettiService = inject(ConfettiService);

protected readonly palette = signal(this._data.palette);
protected readonly state = signal(ExportModalState.FORMAT);
Expand Down Expand Up @@ -81,6 +83,14 @@ export class ExportModalComponent {

if (success) {
this.state.set(ExportModalState.SUCCESS);

// Shoot confetti to celebrate the successful export
this._confettiService.confetti([
{
particleCount: 300,
spread: 140
}
]);
} else {
this.close();
}
Expand Down
23 changes: 23 additions & 0 deletions src/app/shared/data-access/confetti.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TestBed } from '@angular/core/testing';
import { ConfettiService } from './confetti.service';
import { PaletteService, PaletteServiceMock } from './palette.service';

describe('ConfettiService', () => {
let service: ConfettiService;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: PaletteService,
useClass: PaletteServiceMock
}
]
});
service = TestBed.inject(ConfettiService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
38 changes: 38 additions & 0 deletions src/app/shared/data-access/confetti.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { computed, inject, Injectable } from '@angular/core';
import confetti from 'canvas-confetti';
import { PaletteService } from './palette.service';

@Injectable({
providedIn: 'root'
})
export class ConfettiService {
readonly #paletteService = inject(PaletteService);

/**
* Hex colors of the current palette
*/
readonly #colors = computed<Array<string> | undefined>(() => {
// Check if palette is present
const palette = this.#paletteService.palette();
if (!palette || !palette.colors) {
return undefined;
}

// Get all hex codes of all shades in palette
return palette.colors.flatMap((color) => color.shades.map((shade) => shade.hex));
});

/**
* Shoot confetti in the colors of the current palette
*/
public confetti(configs: Array<Omit<confetti.Options, 'colors' | 'zIndex'>>): void {
// Trigger all given confetti configs
for (const config of configs) {
confetti({ ...config, colors: this.#colors(), zIndex: 1001 });
}
}
}

export class ConfettiServiceMock {
public confetti(_configs: Array<Omit<confetti.Options, 'colors' | 'zIndex'>>): void {}
}
4 changes: 3 additions & 1 deletion src/app/shared/data-access/pwa.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TrackingEventAction, TrackingEventCategory } from '../enums/tracking-ev
import { IS_RUNNING_TEST } from '../utils/is-running-test';
import { SwUpdateMock } from '../utils/sw-update-mock';
import { AnalyticsService, AnalyticsServiceMock } from './analytics.service';
import { ConfettiService, ConfettiServiceMock } from './confetti.service';
import { DialogService, DialogServiceMock } from './dialog.service';
import { PaletteService, PaletteServiceMock } from './palette.service';
import { PwaService } from './pwa.service';
Expand Down Expand Up @@ -36,7 +37,8 @@ describe('PwaService', () => {
{ provide: DialogService, useValue: dialogService },
{ provide: ToastService, useValue: toastService },
{ provide: PaletteService, useValue: paletteService },
{ provide: VersionService, useClass: VersionServiceMock }
{ provide: VersionService, useClass: VersionServiceMock },
{ provide: ConfettiService, useClass: ConfettiServiceMock }
]
});
service = TestBed.inject(PwaService);
Expand Down
11 changes: 11 additions & 0 deletions src/app/shared/data-access/pwa.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { LocalStorageKey } from '../enums/local-storage-keys';
import { TrackingEventAction, TrackingEventCategory } from '../enums/tracking-event';
import { IS_RUNNING_TEST } from '../utils/is-running-test';
import { AnalyticsService } from './analytics.service';
import { ConfettiService } from './confetti.service';
import { DialogService } from './dialog.service';
import { PaletteService } from './palette.service';
import { ToastService } from './toast.service';
Expand Down Expand Up @@ -32,6 +33,7 @@ export class PwaService {
private readonly _dialogService = inject(DialogService);
private readonly _paletteService = inject(PaletteService);
private readonly _versionService = inject(VersionService);
private readonly _confettiService = inject(ConfettiService);

private readonly _isPwa = signal(false);
private readonly _doingUpdate = signal(false);
Expand Down Expand Up @@ -67,12 +69,21 @@ export class PwaService {

// Check if the app is currently updating
if (localStorage.getItem(LocalStorageKey.UPGRADING)) {
// Remove upgrading flag from local storage
localStorage.removeItem(LocalStorageKey.UPGRADING);

// Show toast about successful update
this._toastService.showToast({
type: 'info',
message: 'pwa.update-success',
parameters: { version: this._versionService.appVersion }
});

// Shoot confetti to celebrate the update
this._confettiService.confetti([
{ particleCount: 150, spread: 70, angle: 45, origin: { x: 0 }, startVelocity: 75 },
{ particleCount: 150, spread: 70, angle: 135, origin: { x: 1 }, startVelocity: 75 }
]);
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/app/view/view.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ <h1 class="select-none text-3xl font-bold lg:text-2xl">
</button>

<button
[title]="'export.title' | translate"
[disabled]="palette.colors.length === 0"
[title]="(palette.colors.length === 0 ? 'view.palette.no-export' : 'export.title') | translate"
(click)="exportPalette()"
class="flex cursor-pointer items-center gap-2 overflow-hidden rounded bg-blue-500 px-2 py-1.5 font-semibold text-neutral-50 dark:bg-blue-600 sm:px-3 sm:py-2"
class="flex cursor-pointer items-center gap-2 overflow-hidden rounded bg-blue-500 px-2 py-1.5 font-semibold text-neutral-50 disabled:cursor-not-allowed disabled:bg-neutral-400 dark:bg-blue-600 dark:disabled:bg-neutral-700 sm:px-3 sm:py-2"
>
<ng-icon
[svg]="heroArrowDownTrayMini"
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
},
"palette": {
"no-changes": "Es wurden noch keine Änderungen an deiner Palette vorgenommen.",
"no-export": "Deine Palette kann nicht exportiert werden, da sie noch keine Farben enthält.",
"rename": "Gebe deiner Palette einen neuen Namen",
"renamed": "Deine Palette wurde in \"{{ palette }}\" umbenannt.",
"save": "Speichere die Palette in deinem Browser",
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@
},
"palette": {
"no-changes": "No changes have been made to your palette yet.",
"no-export": "Your palette needs at least one color to be exported.",
"rename": "Give your palette a new name",
"renamed": "Your palette has been renamed to \"{{ name }}\".",
"save": "Save your palette in your browser",
Expand Down

0 comments on commit 1858415

Please sign in to comment.