Skip to content

Commit

Permalink
Merge pull request #33 from PAXTabletop/autocomplete
Browse files Browse the repository at this point in the history
autocomplete and 2 step create
  • Loading branch information
Gailbear authored Nov 28, 2022
2 parents 97bbd2f + d4b1d59 commit 2c36c9e
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 18 deletions.
18 changes: 18 additions & 0 deletions supabase-demo/app/src/app/_store/game.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,22 @@ export namespace GameActions {

constructor(public name: string) {}
}

export class Search {
public static readonly type = '[Game] Search';

constructor(public name?: string) {}
}

export namespace Filter {
export class Set {
public static readonly type = '[Game Filter] Set';

constructor(public name?: string) {}
}

export class Reset {
public static readonly type = '[Game Filter] Reset';
}
}
}
47 changes: 41 additions & 6 deletions supabase-demo/app/src/app/_store/game.store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { Action, Selector, State, StateContext } from '@ngxs/store';
import {
Action,
Actions,
NgxsOnInit,
ofActionDispatched,
Selector,
State,
StateContext,
} from '@ngxs/store';
import { createClient, SupabaseClient } from '@supabase/supabase-js';
import { from, map, tap } from 'rxjs';
import { debounceTime, from, map, tap } from 'rxjs';
import { Game } from '../interfaces';
import { GameActions } from './game.actions';
import { insertItem, patch } from '@ngxs/store/operators';
Expand All @@ -11,33 +19,46 @@ import { Injectable } from '@angular/core';

export interface GameStateModel {
games: Game[];
filter: string | undefined;
}

@State<GameStateModel>({
name: 'game',
defaults: {
games: [],
filter: undefined,
},
})
@Injectable()
export class GameState {
export class GameState implements NgxsOnInit {
private supabase: SupabaseClient;

constructor() {
constructor(private readonly actions$: Actions) {
this.supabase = createClient(
environment.supabaseUrl,
environment.supabaseKey
);
}

ngxsOnInit({ dispatch }: StateContext<GameStateModel>): void {
this.actions$
.pipe(ofActionDispatched(GameActions.Search), debounceTime(250))
.subscribe(({ name }) => dispatch(new GameActions.Filter.Set(name)));
}

@Selector()
static games(state: GameStateModel): Game[] {
return state.games;
}

@Action(GameActions.GetAll)
getAllGames({ setState }: StateContext<GameStateModel>) {
return from(this.supabase.from<Game>('game')).pipe(
getAllGames({ setState, getState }: StateContext<GameStateModel>) {
const query = this.supabase.from<Game>('game').select().order('name');
const { filter } = getState();
if (filter) {
query.ilike('name', `%${filter}%`);
}
return from(query).pipe(
map((resp) => {
if (resp.error) {
alert(resp.error.message);
Expand All @@ -48,6 +69,20 @@ export class GameState {
);
}

@Action(GameActions.Filter.Set)
setFilter(
{ setState, dispatch }: StateContext<GameStateModel>,
{ name }: GameActions.Filter.Set
) {
setState(patch({ filter: name }));
return dispatch(new GameActions.GetAll());
}

@Action(GameActions.Filter.Reset)
resetFilter({ dispatch }: StateContext<GameStateModel>) {
return dispatch(new GameActions.Filter.Set(undefined));
}

@Action(GameActions.Create)
createGame(
{ setState }: StateContext<GameStateModel>,
Expand Down
2 changes: 2 additions & 0 deletions supabase-demo/app/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js';
import { environment } from '../environments/environment';
import { MatInputModule } from '@angular/material/input';
import { MatIconModule } from '@angular/material/icon';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { AdminComponent } from './admin/admin.component';
import { ArchiveSessionsComponent } from './archive-sessions/archive-sessions.component';
import { AuthorizationRequiredComponent } from './authorization-required/authorization-required.component';
Expand Down Expand Up @@ -73,6 +74,7 @@ import { AuthorizationRequiredComponent } from './authorization-required/authori
ReactiveFormsModule,
MatInputModule,
MatIconModule,
MatAutocompleteModule,
],
// TODO WIP injectable supabase client
// providers: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.wide-enough {
min-width: 50vw;
}

.w-100 {
width: 100%;
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
<form [formGroup]="createForm" (ngSubmit)="createSession()">
<h1 mat-dialog-title>Create Session</h1>
<div mat-dialog-content>
<mat-form-field>
<h1 mat-dialog-title *ngIf="step === 0">Create Session</h1>
<h1 mat-dialog-title *ngIf="step === 1">Create Session - {{ gameName }}</h1>
<div mat-dialog-content *ngIf="step === 0" class="wide-enough">
<mat-form-field class="w-100">
<mat-label>Game</mat-label>
<mat-select formControlName="game_id">
<mat-option [value]="null">Select a Game</mat-option>
<mat-option *ngFor="let game of games$ | async" [value]="game.game_id">
<input
type="text"
matInput
[formControl]="gameControl"
[matAutocomplete]="auto"
/>
<mat-autocomplete #auto="matAutocomplete" [displayWith]="gameDisplayFn">
<!-- <mat-option [value]="null">Select a Game</mat-option> -->
<mat-option *ngFor="let game of games$ | async" [value]="game">
{{ game.name }}
</mat-option>
</mat-select>
<mat-option [value]="gameControl.value">
Create a new game: {{ gameControl.value }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<br />
</div>
<div mat-dialog-content *ngIf="step === 1">
<mat-form-field>
<mat-label>Filled Seats</mat-label>
<input matInput formControlName="filled_seats" type="number" />
Expand All @@ -27,12 +38,16 @@ <h1 mat-dialog-title>Create Session</h1>
</div>
<div mat-dialog-actions>
<button mat-button mat-dialog-close>Cancel</button>
<button mat-button color="primary" (click)="nextStep()" *ngIf="step === 0">
Next
</button>
<button
mat-button
color="primary"
mat-dialog-close
cdkFocusInitial
(click)="createSession()"
*ngIf="step === 1"
>
Create Session
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Component, Inject } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormControl, Validators } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { map, Observable, mergeMap, Subscription } from 'rxjs';
import { GameActions } from 'src/app/_store/game.actions';
import { Game, NewSession } from '../../interfaces';
import { GameState } from '../../_store/game.store';
import { GameSessionActions } from '../../_store/game_session.actions';
Expand All @@ -12,8 +13,11 @@ import { GameSessionActions } from '../../_store/game_session.actions';
templateUrl: './create-session-dialog.component.html',
styleUrls: ['./create-session-dialog.component.css'],
})
export class CreateSessionDialogComponent {
export class CreateSessionDialogComponent implements OnInit, OnDestroy {
@Select(GameState.games) games$!: Observable<Game[]>;
step = 0;
gameName: string = '';
subscriptions = new Subscription();
createForm = this.fb.group({
game_id: this.fb.nonNullable.control<number>(-1, Validators.required),
event_id: this.fb.nonNullable.control(1, Validators.required),
Expand All @@ -23,16 +27,72 @@ export class CreateSessionDialogComponent {
location: this.fb.control(''),
});

gameControl = this.fb.control<string | Game>('');

get gameIdControl() {
return this.createForm.get('game_id') as FormControl;
}

constructor(
public dialogRef: MatDialogRef<CreateSessionDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private readonly store: Store,
private readonly fb: FormBuilder
) {}

ngOnInit(): void {
this.step = 0;
this.subscriptions.add(
this.gameControl.valueChanges.subscribe((gameControlValue) => {
const name =
typeof gameControlValue === 'string'
? gameControlValue || ''
: gameControlValue?.name;
this.store.dispatch(new GameActions.Search(name));
})
);
this.subscriptions.add(
this.dialogRef.beforeClosed().subscribe(() => {
this.store.dispatch(new GameActions.Filter.Reset());
})
);
}

ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}

gameDisplayFn(game: Game | string) {
return typeof game === 'string' ? game : game.name;
}

createSession() {
this.store.dispatch(
new GameSessionActions.Create(this.createForm.value as NewSession)
);
}

nextStep() {
if (typeof this.gameControl.value === 'string') {
const newGameName = this.gameControl.value;
this.gameName = newGameName;
this.subscriptions.add(
this.store
.dispatch(new GameActions.Create(newGameName))
.pipe(
mergeMap(() => this.games$),
map((games) => games.find((g) => g.name === newGameName))
)
.subscribe((newGame) => {
if (newGame?.game_id)
this.createForm.patchValue({ game_id: newGame.game_id });
})
);
} else if (this.gameControl.value) {
const game: Game = this.gameControl.value;
this.gameName = game.name;
this.gameIdControl.setValue(game.game_id);
}
this.step = 1;
}
}

0 comments on commit 2c36c9e

Please sign in to comment.