Skip to content

Commit

Permalink
feat(website): home page, user page and player
Browse files Browse the repository at this point in the history
- New home page design
- New user page design
  - Player
  - Chat

- Adds `display_color` to user
  - will be randomly generated when registering
- `activeStreamsByUserId` GQL endpoint to fetch all active streams
  by user id (this may become obsolete with the new data structure soon)
  • Loading branch information
lennartkloock committed Aug 23, 2023
1 parent dcfb9ef commit ba74188
Show file tree
Hide file tree
Showing 81 changed files with 9,136 additions and 1,991 deletions.

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

16 changes: 16 additions & 0 deletions backend/api/src/api/v1/gql/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ impl Query {

Ok(user.map(models::user::User::from))
}

async fn active_streams_by_user_id(
&self,
ctx: &Context<'_>,
#[graphql(desc = "The id of the user.")] id: Uuid,
) -> Result<Option<models::stream::Stream>> {
let global = ctx.get_global();

let stream = global
.active_streams_by_user_id_loader
.load_one(id)
.await
.map_err_gql("failed to fetch stream")?;

Ok(stream.map(models::stream::Stream::from))
}
}

pub type MySchema = Schema<Query, Mutation, subscription::Subscription>;
Expand Down
1 change: 1 addition & 0 deletions backend/api/src/api/v1/gql/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pub mod chat_message;
pub mod date;
pub mod global_roles;
pub mod session;
pub mod stream;
pub mod user;
37 changes: 37 additions & 0 deletions backend/api/src/api/v1/gql/models/stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use async_graphql::SimpleObject;
use uuid::Uuid;

use crate::database::stream;

use super::date::DateRFC3339;

#[derive(SimpleObject, Clone)]
pub struct Stream {
pub id: Uuid,
pub channel_id: Uuid,
pub title: String,
pub description: String,
pub recorded: bool,
pub transcoded: bool,
pub deleted: bool,
pub ready_state: i64,
pub created_at: DateRFC3339,
pub ended_at: DateRFC3339,
}

impl From<stream::Model> for Stream {
fn from(value: stream::Model) -> Self {
Self {
id: value.id,
channel_id: value.channel_id,
title: value.title,
description: value.description,
recorded: value.recorded,
transcoded: value.transcoded,
deleted: value.deleted,
ready_state: value.ready_state.into(),
created_at: value.created_at.into(),
ended_at: value.ended_at.into(),
}
}
}
3 changes: 3 additions & 0 deletions backend/api/src/api/v1/gql/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use super::{date::DateRFC3339, global_roles::GlobalRole};
pub struct User {
pub id: Uuid,
pub display_name: String,
pub display_color: i32,
pub username: String,
pub created_at: DateRFC3339,

Expand Down Expand Up @@ -164,6 +165,8 @@ impl From<user::Model> for User {
id: value.id,
username: value.username,
display_name: value.display_name,
// THIS IS TEMPORARY
display_color: user::generate_display_color(),
email_: value.email,
email_verified_: value.email_verified,
created_at: value.created_at.into(),
Expand Down
27 changes: 27 additions & 0 deletions backend/api/src/database/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,30 @@ pub fn generate_stream_key() -> String {

key
}

/// https://www.rapidtables.com/convert/color/hsl-to-rgb.html
fn hsl_to_rgb(h: u16, s: f64, l: f64) -> (u8, u8, u8) {
let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
let x = c * (1.0 - ((h as f64 / 60.0) % 2.0 - 1.0).abs());
let m = l - c / 2.0;
let (r, g, b) = match h {
0..=59 => (c, x, 0.0),
60..=119 => (x, c, 0.0),
120..=179 => (0.0, c, x),
180..=239 => (0.0, x, c),
240..=299 => (x, 0.0, c),
300..=359 => (c, 0.0, x),
_ => (0.0, 0.0, 0.0),
};

(
((r + m) * 255.0) as u8,
((g + m) * 255.0) as u8,
((b + m) * 255.0) as u8,
)
}

pub fn generate_display_color() -> i32 {
let (r, g, b) = hsl_to_rgb(rand::thread_rng().gen_range(0..=359), 1.0, 0.67);
((r as i32) << 16) + ((g as i32) << 8) + b as i32
}
39 changes: 39 additions & 0 deletions backend/api/src/dataloader/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,42 @@ impl Loader<Uuid> for StreamByIdLoader {
Ok(map)
}
}

/// Fetches the active stream for each user in the given list.
pub struct ActiveStreamsByUserIdLoader {
db: Arc<sqlx::PgPool>,
}

impl ActiveStreamsByUserIdLoader {
pub fn new(db: Arc<sqlx::PgPool>) -> DataLoader<Self> {
DataLoader::new(Self { db }, tokio::spawn)
}
}

#[async_trait]
impl Loader<Uuid> for ActiveStreamsByUserIdLoader {
type Value = stream::Model;
type Error = Arc<sqlx::Error>;

async fn load(&self, keys: &[Uuid]) -> Result<HashMap<Uuid, Self::Value>, Self::Error> {
let results = sqlx::query_as!(
stream::Model,
"SELECT * FROM streams WHERE channel_id = ANY($1) AND deleted = false AND ready_state = 1 ORDER BY created_at DESC",
&keys
)
.fetch_all(&*self.db)
.await
.map_err(|e| {
tracing::error!("Failed to fetch streams: {}", e);
Arc::new(e)
})?;

let mut map = HashMap::new();

for result in results {
map.insert(result.channel_id, result);
}

Ok(map)
}
}
4 changes: 3 additions & 1 deletion backend/api/src/global/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use fred::pool::RedisPool;
use fred::prelude::ClientLike;
use fred::types::{ReconnectPolicy, RedisConfig, ServerConfig};

use crate::dataloader::stream::StreamByIdLoader;
use crate::dataloader::stream::{ActiveStreamsByUserIdLoader, StreamByIdLoader};
use crate::dataloader::user_permissions::UserPermissionsByIdLoader;
use crate::dataloader::{
session::SessionByIdLoader, user::UserByIdLoader, user::UserByUsernameLoader,
Expand All @@ -29,6 +29,7 @@ pub struct GlobalState {
pub session_by_id_loader: DataLoader<SessionByIdLoader>,
pub user_permisions_by_id_loader: DataLoader<UserPermissionsByIdLoader>,
pub stream_by_id_loader: DataLoader<StreamByIdLoader>,
pub active_streams_by_user_id_loader: DataLoader<ActiveStreamsByUserIdLoader>,
pub subscription_manager: SubscriptionManager,
pub rmq: common::rmq::ConnectionPool,
pub redis: RedisPool,
Expand All @@ -50,6 +51,7 @@ impl GlobalState {
session_by_id_loader: SessionByIdLoader::new(db.clone()),
user_permisions_by_id_loader: UserPermissionsByIdLoader::new(db.clone()),
stream_by_id_loader: StreamByIdLoader::new(db.clone()),
active_streams_by_user_id_loader: ActiveStreamsByUserIdLoader::new(db.clone()),
subscription_manager: SubscriptionManager::default(),
db,
rmq,
Expand Down
42 changes: 42 additions & 0 deletions design/definitions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Users, Channels, Streams and Orgs @ Scuffle

This is different from Twitch. For Twitch a user is essentially the same as a channel.

## User

A user is what you create when you register at Scuffle.
A user has a username, display name, chat color and profile picture. (and probably some other stuff too)
The display name can differ from the username only in capitalization.
Your display name is what shows up in chat.
A user doesn't have an url.
A user can either have zero or one channel.
By default a user doesn't have a channel.

## Channel

A user can create one channel that is associated to their user account.
A channel has a display name.
The url of a channel is the username of the user it belongs to.
You can change how your channel home page looks.

## Stream

A channel can have multiple streams.
A stream is something a channel can go live on.
Streams are persistent.
Streams have a name.

Each stream has a separate

- chat
- offline banner
- stream key

## Recording

A recording is a video of a past stream. (VOD)

## Org

An org is a special user/channel which other channels can join.
It can enforce guidelines on its members.
2 changes: 1 addition & 1 deletion frontend/player/demo/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { init, Player } from "../js/main";
import init, { Player } from "../pkg/player";

await init();

Expand Down
4 changes: 0 additions & 4 deletions frontend/player/js/main.ts

This file was deleted.

16 changes: 7 additions & 9 deletions frontend/player/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@
"wasm:watch": "cargo watch --watch src --watch Cargo.toml -s \"pnpm run wasm:build --dev\"",
"watch": "pnpm run wasm:watch & vite build --watch",
"update": "pnpm update && cargo update",
"build": "pnpm run clean && pnpm run wasm:build --release && tsc && vite build && vite build -c vite.demo.config.ts",
"build:dev": "pnpm run clean && pnpm run wasm:build --dev && tsc && vite build && vite build -c vite.demo.config.ts",
"lint": "prettier --check \"**/*\" -u && eslint . --ext .js,.ts && cargo fmt --check && cargo clippy -- -D warnings",
"format": "prettier --write \"**/*\" -u && cargo fmt && cargo clippy --fix --allow-dirty --allow-staged",
"demo:dev": "pnpm run wasm:watch & vite",
"demo:build": "pnpm run wasm:build --release && tsc && vite build -c vite.demo.config.ts",
"demo:preview": "vite preview -c vite.demo.config.ts",
"clean": "rimraf dist pkg"
"dev": "pnpm run wasm:watch & vite",
"build": "pnpm run wasm:build --release && tsc && vite build",
"preview": "vite preview",
"clean": "rimraf pkg"
},
"module": "./dist/player.js",
"types": "./dist/player.d.ts",
"module": "./pkg/player.js",
"types": "./pkg/player.d.ts",
"files": [
"dist"
"pkg"
],
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.0.0",
Expand Down
8 changes: 7 additions & 1 deletion frontend/player/src/player/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mod util;
#[wasm_bindgen(typescript_custom_section)]
const _: &'static str = r#"
type PlayerEvents = {
error: (evt: ErrorEvent) => void;
error: (evt: EventError) => void;
load: (evt: EventLoad) => void;
manifestloaded: (evt: EventManifestLoaded) => void;
variantchange: (evt: EventVariantChange) => void;
Expand All @@ -38,6 +38,7 @@ class Player {
load(url: string): void;
attach(el: HTMLVideoElement): void;
detach(): void;
shutdown(): void;
on<K extends keyof PlayerEvents>(event: K, f: PlayerEvents[K]): void;
Expand Down Expand Up @@ -176,6 +177,11 @@ impl Player {
Ok(())
}

pub fn detach(&mut self) {
self.shutdown();
self.inner.set_video_element(None);
}

pub fn shutdown(&self) {
self.shutdown_sender.send(()).ok();
}
Expand Down
Loading

0 comments on commit ba74188

Please sign in to comment.