Skip to content

Commit

Permalink
rewrite frontend to use signals (#12)
Browse files Browse the repository at this point in the history
- rewrite frontend to use signals
- bump version
- minor UI tweaks
  - switch keybind generator select checkbox color to mat-accent
  - make error snackbars consistent
  - fix vertical overflow of header buttons on mobile
  - update some texts and snackbars
  - add alt tag to avatar in header
- fix local playback button incorrectly appearing on random buttons
- typography improvements: ditch title case, always refer to the Discord guild as server
- switch to new functional guards API
  • Loading branch information
dominikks authored Nov 6, 2023
1 parent 022c4e2 commit 5f6a2bf
Show file tree
Hide file tree
Showing 77 changed files with 1,416 additions and 1,674 deletions.
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,24 @@
/backend/data
.env
.volumes

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# System Files
.DS_Store
Thumbs.db
2 changes: 1 addition & 1 deletion backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "discord-soundboard-bot"
version = "0.3.3"
version = "0.4.0-dev"
authors = ["Dominik Kus <[email protected]>"]
edition = "2021"

Expand Down
7 changes: 4 additions & 3 deletions backend/src/api/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ struct SessionInfo {
impl<'r> FromRequest<'r> for UserId {
type Error = ();

/// Protected api endpoints can inject `User`.
/// Protected api endpoints can inject `UserId`.
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let cookies = request.cookies();
cookies
Expand Down Expand Up @@ -157,7 +157,7 @@ impl From<TokenUserId> for SerenityUserId {
impl<'r> FromRequest<'r> for TokenUserId {
type Error = ();

/// Protected api endpoints can inject `User`.
/// Protected api endpoints can inject `TokenUserId` to be accessible via auth token or session.
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
const TOKEN_PREFIX: &str = "Bearer ";
let db = try_outcome!(request.guard::<DbConn>().await);
Expand Down Expand Up @@ -199,6 +199,7 @@ impl<'r> FromRequest<'r> for TokenUserId {
}
}

// If there is no auth token, we try to parse a normal UserId from a session
request
.guard::<UserId>()
.await
Expand Down Expand Up @@ -456,7 +457,7 @@ fn logout(cookies: &CookieJar<'_>) -> String {
String::from("User logged out")
}

/// Beware: this replaces the current auth token by a new one. The old one becomes invalid.
/// Beware: this replaces the current auth token with a new one. The old one becomes invalid.
#[post("/auth/gettoken")]
async fn get_auth_token(user: UserId, db: DbConn) -> Result<String, AuthError> {
let uid = BigDecimal::from_u64(user.0).ok_or_else(AuthError::bigdecimal_error)?;
Expand Down
30 changes: 17 additions & 13 deletions backend/src/api/settings.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
use crate::api::Snowflake;
use crate::api::UserId;
use crate::db::models;
use crate::db::DbConn;
use crate::discord::management::check_guild_admin;
use crate::discord::management::check_guild_moderator;
use crate::discord::management::get_guilds_for_user;
use crate::discord::management::PermissionError;
use crate::CacheHttp;
use core::convert::TryFrom;
use std::collections::HashMap;

use bigdecimal::BigDecimal;
use bigdecimal::FromPrimitive;
use bigdecimal::ToPrimitive;
use core::convert::TryFrom;
use diesel::prelude::*;
use diesel::result::Error as DieselError;
use rocket::serde::json::Json;
Expand All @@ -19,7 +12,14 @@ use rocket::State;
use serde::Deserialize;
use serde::Serialize;
use serenity::model::id::GuildId;
use std::collections::HashMap;

use crate::api::Snowflake;
use crate::api::UserId;
use crate::db::models;
use crate::db::DbConn;
use crate::discord::management::PermissionError;
use crate::discord::management::{check_guild_admin, check_guild_moderator, get_guilds_for_user};
use crate::CacheHttp;

pub fn get_routes() -> Vec<Route> {
routes![
Expand Down Expand Up @@ -90,7 +90,7 @@ impl TryFrom<models::RandomInfix> for RandomInfix {
}
}

#[get("/randominfixes")]
#[get("/random-infixes")]
async fn get_all_random_infixes(
user: UserId,
db: DbConn,
Expand Down Expand Up @@ -127,7 +127,11 @@ struct RandomInfixParameter {
display_name: String,
}

#[put("/guilds/<guild_id>/randominfixes", format = "json", data = "<params>")]
#[put(
"/guilds/<guild_id>/random-infixes",
format = "json",
data = "<params>"
)]
async fn set_random_infixes(
guild_id: u64,
user: UserId,
Expand Down
2 changes: 1 addition & 1 deletion frontend/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"@typescript-eslint/member-ordering": [
"error",
{
"default": ["signature", "field", "constructor", "method"]
"default": ["static-field", "static-initialization", "static-method", "signature", "field", "constructor", "method"]
}
],
"@typescript-eslint/no-inferrable-types": [
Expand Down
20 changes: 0 additions & 20 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,6 @@
chrome-profiler-events*.json
speed-measure-plugin*.json

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# misc
/.angular/cache
/.sass-cache
Expand All @@ -42,6 +25,3 @@ yarn-error.log
testem.log
/typings

# System Files
.DS_Store
Thumbs.db
16 changes: 8 additions & 8 deletions frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { GuildPermissionGuard } from './guards/guild-permission.guard';
import { RouterModule, Routes } from '@angular/router';
import { guildPermissionGuard } from './guards/guild-permission.guard';
import { KeybindGeneratorComponent } from './keybind-generator/keybind-generator.component';
import { RecorderComponent } from './recorder/recorder.component';
import { CanDeactivateGuildSettingsGuard } from './settings/guild-settings/can-deactivate-guild-settings.guard';
import { GuildSettingsComponent } from './settings/guild-settings/guild-settings.component';
import { SettingsComponent } from './settings/settings.component';
import { CanDeactivateSoundManagerGuard } from './settings/sound-manager/can-deactivate-sound-manager.guard';
import { canDeactivateSoundManagerGuard } from './settings/sound-manager/can-deactivate-sound-manager.guard';
import { SoundManagerComponent } from './settings/sound-manager/sound-manager.component';
import { UserSettingsComponent } from './settings/user-settings/user-settings.component';
import { SoundboardComponent } from './soundboard/soundboard.component';
import { canDeactivateGuildSettingsGuard } from './settings/guild-settings/can-deactivate-guild-settings.guard';

const routes: Routes = [
{
Expand Down Expand Up @@ -39,7 +39,7 @@ const routes: Routes = [
},
{
path: 'guilds/:guildId',
canActivate: [GuildPermissionGuard],
canActivate: [guildPermissionGuard],
children: [
{
path: '',
Expand All @@ -49,12 +49,12 @@ const routes: Routes = [
{
path: 'settings',
component: GuildSettingsComponent,
canDeactivate: [CanDeactivateGuildSettingsGuard],
canDeactivate: [canDeactivateGuildSettingsGuard],
},
{
path: 'sounds',
component: SoundManagerComponent,
canDeactivate: [CanDeactivateSoundManagerGuard],
canDeactivate: [canDeactivateSoundManagerGuard],
},
],
},
Expand All @@ -67,7 +67,7 @@ const routes: Routes = [
];

@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: false })],
imports: [RouterModule.forRoot(routes, { useHash: false, bindToComponentInputs: true })],
exports: [RouterModule],
})
export class AppRoutingModule {}
11 changes: 3 additions & 8 deletions frontend/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
<ng-container *ngIf="loggedIn$ | async; else needsLogin">
<router-outlet></router-outlet>
<ng-container *appDataLoad="data$; callback: loadedData$" [ngSwitch]="apiService.user() != null">
<router-outlet *ngSwitchCase="true"></router-outlet>
<app-login *ngSwitchCase="false"></app-login>
</ng-container>

<ng-template #needsLogin>
<app-login></app-login>
</ng-template>

<app-error-box></app-error-box>
21 changes: 14 additions & 7 deletions frontend/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { Component } from '@angular/core';
import { LoginService } from './services/login.service';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { forkJoin, of, Subject } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ApiService, AppInfo, User } from './services/api.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
state: 'loading' | 'finished' | 'error' = 'loading';
get loggedIn$() {
return this.loginService.loggedIn$;
}
readonly data$ = forkJoin([this.apiService.loadAppInfo(), this.apiService.loadUser().pipe(catchError(() => of(null)))]);
readonly loadedData$ = new Subject<[AppInfo, User]>();

constructor(private loginService: LoginService) {}
constructor(protected apiService: ApiService) {
this.loadedData$.pipe(takeUntilDestroyed()).subscribe(data => {
this.apiService.appInfo.set(data[0]);
this.apiService.user.set(data[1]);
});
}
}
14 changes: 8 additions & 6 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BrowserModule } from '@angular/platform-browser';
import { LOCALE_ID, NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
Expand All @@ -9,7 +9,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSliderModule } from '@angular/material/slider';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSnackBarModule, MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatRippleModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatTableModule } from '@angular/material/table';
Expand All @@ -30,7 +30,7 @@ import { WebAudioModule } from '@ng-web-apis/audio';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { SoundboardButtonComponent } from './soundboard/soundboard-button/soundboard-button.component';
import { KeybindGeneratorComponent } from './keybind-generator/keybind-generator.component';
import { KeycombinationInputComponent } from './keybind-generator/keycombination-input/keycombination-input.component';
import { KeyCombinationInputComponent } from './keybind-generator/keycombination-input/key-combination-input.component';
import { SearchableSoundSelectComponent } from './keybind-generator/searchable-sound-select/searchable-sound-select.component';
import { RecorderComponent } from './recorder/recorder.component';
import { FooterComponent } from './footer/footer.component';
Expand All @@ -42,7 +42,6 @@ import { UserSettingsComponent } from './settings/user-settings/user-settings.co
import { GuildSettingsComponent } from './settings/guild-settings/guild-settings.component';
import { RandomInfixesComponent } from './settings/random-infixes/random-infixes.component';
import { UnsavedChangesBoxComponent } from './settings/unsaved-changes-box/unsaved-changes-box.component';
import { ErrorBoxComponent } from './error-box/error-box.component';
import { SoundManagerComponent } from './settings/sound-manager/sound-manager.component';
import { SoundDetailsComponent } from './settings/sound-manager/sound-details/sound-details.component';
import { SoundDeleteConfirmComponent } from './settings/sound-manager/sound-delete-confirm/sound-delete-confirm.component';
Expand All @@ -54,14 +53,16 @@ import { EventLogDialogComponent } from './soundboard/event-log-dialog/event-log
import { ScrollIntoViewDirective } from './common/scroll-into-view.directive';
import { EventDescriptionPipe } from './event-description.pipe';
import { VolumeSliderComponent } from './volume-slider/volume-slider.component';
import { DataLoadDirective } from './data-load/data-load.directive';
import { DataLoadErrorComponent } from './data-load/data-load-error.component';

@NgModule({
declarations: [
AppComponent,
SoundboardComponent,
SoundboardButtonComponent,
KeybindGeneratorComponent,
KeycombinationInputComponent,
KeyCombinationInputComponent,
SearchableSoundSelectComponent,
RecorderComponent,
FooterComponent,
Expand All @@ -72,7 +73,6 @@ import { VolumeSliderComponent } from './volume-slider/volume-slider.component';
GuildSettingsComponent,
RandomInfixesComponent,
UnsavedChangesBoxComponent,
ErrorBoxComponent,
SoundManagerComponent,
SoundDetailsComponent,
SoundDeleteConfirmComponent,
Expand All @@ -81,6 +81,8 @@ import { VolumeSliderComponent } from './volume-slider/volume-slider.component';
ScrollIntoViewDirective,
EventDescriptionPipe,
VolumeSliderComponent,
DataLoadDirective,
DataLoadErrorComponent,
],
imports: [
// Angular
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/common/scroll-into-view.directive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Directive, AfterViewInit, Input, ElementRef } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';

@Directive({
selector: '[appScrollIntoView]',
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/app/data-load/data-load-error.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';

@Component({
template: `
<div class="error-box">
<mat-icon>error</mat-icon>
<div>There was an error loading the required data. Please try again later.</div>
</div>
<button mat-button (click)="retry.emit()"><mat-icon>refresh</mat-icon> Retry</button>
`,
styles: [
`
@use '@angular/material' as mat;
:host {
@include mat.elevation(4);
background-color: rgba(255, 0, 0, 0.2);
border-radius: 5px;
display: block;
max-width: 400px;
margin: 16px auto;
padding: 16px;
}
.error-box {
display: flex;
align-items: center;
margin-bottom: 8px;
gap: 8px;
mat-icon {
flex-shrink: 0;
}
}
`,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DataLoadErrorComponent {
@Output() retry = new EventEmitter<void>();
}
Loading

0 comments on commit 5f6a2bf

Please sign in to comment.