filterbar wip

This commit is contained in:
Robin Appelman 2023-04-11 23:06:48 +02:00
commit e5c9aeb7fe
15 changed files with 1905 additions and 420 deletions

795
Cargo.lock generated

File diff suppressed because it is too large Load diff

1263
build/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,23 +3,32 @@ name = "demostf-build"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[lib]
[[bin]]
path = "src/bundle_script.rs"
name = "script"
[dependencies] [dependencies]
tracing-subscriber = "0.3.16"
lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] } lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] }
base64 = "0.21.0" base64 = "0.21.0"
urlencoding = "2.1.2" urlencoding = "2.1.2"
const-fnv1a-hash = "1.1.0" const-fnv1a-hash = "1.1.0"
swc = "0.259.6" swc = "0.259.6"
swc_common = { version = "0.30.5", features = ["tty-emitter"] } swc_common = { version = "0.30.5", features = ["tty-emitter", "concurrent"], path = "../../../rust/swc/crates/swc_common" }
swc_bundler = "0.212.5" #swc_bundler = "0.212.5"
swc_ecma_loader = "0.42.5" swc_bundler = { version = "0.212.5", path = "../../../rust/swc/crates/swc_bundler" }
swc_ecma_ast = "0.102.5" #swc_ecma_loader = "0.42.5"
swc_atoms = "0.4.43" swc_ecma_loader = { version = "0.42.5", path = "../../../rust/swc/crates/swc_ecma_loader", features = ["node", "cache"] }
swc_ecma_parser = { version = "0.132.6", features = ["typescript"] } swc_ecma_ast = { version = "0.102.5", path = "../../../rust/swc/crates/swc_ecma_ast" }
swc_ecma_codegen = "0.137.6" swc_atoms = { version = "0.4.43", path = "../../../rust/swc/crates/swc_atoms" }
swc_ecma_transforms_base = "0.125.1" swc_ecma_parser = { version = "0.132.6", features = ["typescript"], path = "../../../rust/swc/crates/swc_ecma_parser" }
swc_ecma_transforms_typescript = "0.175.4" swc_ecma_codegen = { version = "0.137.6", path = "../../../rust/swc/crates/swc_ecma_codegen" }
swc_ecma_visit = "0.88.5" swc_ecma_transforms_base = { version = "0.125.1", path = "../../../rust/swc/crates/swc_ecma_transforms_base" }
swc_ecma_transforms_typescript = { version = "0.175.4", path = "../../../rust/swc/crates/swc_ecma_transforms_typescript" }
swc_ecma_visit = { version = "0.88.5", path = "../../../rust/swc/crates/swc_ecma_visit" }
anyhow = "1.0.70" anyhow = "1.0.70"
jsx-dom-expressions = { version = "0.1", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions" } #jsx-dom-expressions = { version = "0.1", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions" }
#jsx-dom-expressions = { version = "0.1", path = "../../../rust/swc-plugin-jsx-dom-expressions" } jsx-dom-expressions = { version = "0.1", path = "../../../rust/swc-plugin-jsx-dom-expressions" }

View file

@ -0,0 +1,11 @@
use std::env::args;
mod script;
fn main() {
tracing_subscriber::fmt::init();
let path = args().skip(1).next().unwrap();
let output = script::bundle_script(&path);
// println!("{output}")
}

View file

@ -156,7 +156,7 @@ impl Load for Loader {
.fold_with(&mut strip(top_level_mark)) .fold_with(&mut strip(top_level_mark))
.fold_with(&mut as_folder(TransformVisitor::new( .fold_with(&mut as_folder(TransformVisitor::new(
jsx_dom_expressions::config::Config { jsx_dom_expressions::config::Config {
module_name: "solid-js/web/dist/web.js".to_string(), module_name: "solid-js/web".to_string(),
builtins: vec![ builtins: vec![
"For".into(), "For".into(),
"Show".into(), "Show".into(),

38
script/api.ts Normal file
View file

@ -0,0 +1,38 @@
export type SteamId = string;
export interface SteamUser {
id: number;
steamid: SteamId;
name: string;
}
export class Api {
private base: string;
constructor(base: string) {
this.base = base;
}
getApiUrl(url) {
return this.base + url;
}
request(url, params = {}, json = true): Promise<string | any> {
let queryParams = new URLSearchParams(params);
return fetch(this.getApiUrl(url) + '?' + queryParams)
.then((response) => {
if (json) {
return response.json()
} else {
return response.text();
}
});
}
async searchPlayer(query: string): Promise<SteamUser[]> {
if (query.length < 2) {
return [];
}
return await this.request('users/search', {query}) as SteamUser[];
}
}

View file

@ -1,7 +1,13 @@
import {render} from "solid-js/web/dist/web.js"; import {render} from "solid-js/web";
import {ready} from "./ready"; import {ready} from "./ready";
import {FilterBar} from "./filterbar" import {FilterBar} from "./filterbar"
import {Api} from "./api";
ready(() => { ready(() => {
render(() => <FilterBar name="World" />, document.querySelector('.filter-bar')) const filterBar = document.getElementById('filter-bar');
const maps = filterBar.dataset.maps.split(",");
const apiBase = filterBar.dataset.apiBase;
const api = new Api(apiBase);
render(() => <FilterBar maps={maps} api={api} />, filterBar)
}); });

View file

@ -1,3 +0,0 @@
export const FilterBar = ({name}) => {
return <div>Hello {name}!</div>;
}

19
script/filterbar.tsx Normal file
View file

@ -0,0 +1,19 @@
import {Select, createOptions, createAsyncOptions} from "@thisbeyond/solid-select";
import {Api} from "./api";
export interface FilterBarProps {
maps: string[],
api: Api,
}
export const FilterBar = ({maps, api}: FilterBarProps) => {
const modes = createOptions(["4v4", "6v6", "Highlander"]);
const mapOptions = createOptions(maps);
const playerOptions = createAsyncOptions(search => api.searchPlayer(search));
const playerFormat = player => player.name;
return <div class="filter-bar">
<Select class="mode" placeholder="All Types" {...modes} />
<Select class="maps" placeholder="All Maps" {...mapOptions} />
<Select class="players" multiple placeholder="All Players" format={playerFormat} {...playerOptions} />
</div>;
}

19
src/data/maps.rs Normal file
View file

@ -0,0 +1,19 @@
use crate::Result;
use sqlx::{query, Executor, Postgres};
use tracing::instrument;
#[instrument(skip(connection))]
pub async fn map_list(
connection: impl Executor<'_, Database = Postgres>,
) -> Result<impl Iterator<Item = String>> {
Ok(query!(
r#"SELECT
map as "map!"
FROM map_list
ORDER BY count DESC LIMIT 50"#
)
.fetch_all(connection)
.await?
.into_iter()
.map(|res| res.map))
}

View file

@ -1,5 +1,6 @@
pub mod chat; pub mod chat;
pub mod demo; pub mod demo;
pub mod maps;
pub mod player; pub mod player;
pub mod steam_id; pub mod steam_id;
pub mod user; pub mod user;

View file

@ -8,6 +8,7 @@ mod session;
pub use crate::config::Config; pub use crate::config::Config;
use crate::config::Listen; use crate::config::Listen;
use crate::data::demo::{Demo, ListDemo}; use crate::data::demo::{Demo, ListDemo};
use crate::data::maps::map_list;
use crate::data::steam_id::SteamId; use crate::data::steam_id::SteamId;
use crate::data::user::User; use crate::data::user::User;
use crate::pages::about::AboutPage; use crate::pages::about::AboutPage;
@ -127,7 +128,15 @@ async fn main() -> Result<()> {
async fn index(State(app): State<Arc<App>>, session: SessionData) -> Result<Markup> { async fn index(State(app): State<Arc<App>>, session: SessionData) -> Result<Markup> {
let demos = ListDemo::list(&app.connection, None).await?; let demos = ListDemo::list(&app.connection, None).await?;
Ok(render(Index { demos }, session)) let maps = map_list(&app.connection).await?.collect();
Ok(render(
Index {
demos,
maps,
api: &app.api,
},
session,
))
} }
async fn about(State(_app): State<Arc<App>>, session: SessionData) -> Result<Markup> { async fn about(State(_app): State<Arc<App>>, session: SessionData) -> Result<Markup> {

View file

@ -1,14 +1,16 @@
use crate::asset::saved_asset_url; use crate::asset::saved_asset_url;
use crate::data::demo::ListDemo; use crate::data::demo::ListDemo;
use crate::pages::Page; use crate::pages::Page;
use maud::{html, Markup}; use maud::{html, Markup, Render};
use std::borrow::Cow; use std::borrow::Cow;
pub struct Index { pub struct Index<'a> {
pub demos: Vec<ListDemo>, pub demos: Vec<ListDemo>,
pub maps: Vec<String>,
pub api: &'a str,
} }
impl Page for Index { impl Page for Index<'_> {
fn title(&self) -> Cow<'static, str> { fn title(&self) -> Cow<'static, str> {
"Demos - demos.tf".into() "Demos - demos.tf".into()
} }
@ -17,7 +19,7 @@ impl Page for Index {
let script = saved_asset_url!("demo_list.js"); let script = saved_asset_url!("demo_list.js");
html! { html! {
h1 { "Demos" } h1 { "Demos" }
.filter-bar {} #filter-bar data-maps = (MapList(&self.maps)) data-api-base = (self.api) {}
table.demolist { table.demolist {
thead { thead {
tr { tr {
@ -46,3 +48,18 @@ impl Page for Index {
} }
} }
} }
struct MapList<'a>(&'a [String]);
impl Render for MapList<'_> {
fn render_to(&self, buffer: &mut String) {
let mut first = true;
for map in self.0 {
if !first {
buffer.push_str(",");
}
buffer.push_str(&map);
first = false;
}
}
}

77
style/filterbar.css Normal file
View file

@ -0,0 +1,77 @@
@import '../node_modules/@thisbeyond/solid-select/dist/esm/style.css';
.filter-bar {
display: flex;
flex-direction: row;
& .solid-select-container {
color: var(--text-primary);
}
& .solid-select-control {
outline-color: var(--highlight-primary);
border: var(--text-secondary) 1px solid;
}
& .solid-select-placeholder {
color: var(--text-secondary);
}
& .solid-select-option {
color: var(--text-primary);
&:hover {
background-color: var(--primary-color-accent);
}
&[data-focused=true] {
background-color: var(--highlight-primary);
}
}
& .solid-select-list {
background-color: var(--primary-color);
border: var(--text-secondary) 1px solid;
}
& .solid-select-multi-value {
padding-right: 0;
}
& .solid-select-multi-value-remove {
margin-left: 5px;
}
& > div {
display: inline-block;
flex-grow: 0;
}
& > .mode {
width: 150px;
& .solid-select-control {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
& > .maps {
width: 200px;
& .solid-select-control {
border-right: none;
border-left: none;
border-radius: 0;
}
}
& > .players {
flex-grow: 1;
& .solid-select-control {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}

View file

@ -1,3 +1,5 @@
@import '../filterbar.css';
.demolist { .demolist {
width: 100%; width: 100%;
text-align: left; text-align: left;