mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
filterbar wip
This commit is contained in:
parent
10ea8ddcbc
commit
e5c9aeb7fe
15 changed files with 1905 additions and 420 deletions
795
Cargo.lock
generated
795
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
1263
build/Cargo.lock
generated
1263
build/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -3,23 +3,32 @@ name = "demostf-build"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
[[bin]]
|
||||
path = "src/bundle_script.rs"
|
||||
name = "script"
|
||||
|
||||
[dependencies]
|
||||
tracing-subscriber = "0.3.16"
|
||||
lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] }
|
||||
base64 = "0.21.0"
|
||||
urlencoding = "2.1.2"
|
||||
const-fnv1a-hash = "1.1.0"
|
||||
swc = "0.259.6"
|
||||
swc_common = { version = "0.30.5", features = ["tty-emitter"] }
|
||||
swc_bundler = "0.212.5"
|
||||
swc_ecma_loader = "0.42.5"
|
||||
swc_ecma_ast = "0.102.5"
|
||||
swc_atoms = "0.4.43"
|
||||
swc_ecma_parser = { version = "0.132.6", features = ["typescript"] }
|
||||
swc_ecma_codegen = "0.137.6"
|
||||
swc_ecma_transforms_base = "0.125.1"
|
||||
swc_ecma_transforms_typescript = "0.175.4"
|
||||
swc_ecma_visit = "0.88.5"
|
||||
swc_common = { version = "0.30.5", features = ["tty-emitter", "concurrent"], path = "../../../rust/swc/crates/swc_common" }
|
||||
#swc_bundler = "0.212.5"
|
||||
swc_bundler = { version = "0.212.5", path = "../../../rust/swc/crates/swc_bundler" }
|
||||
#swc_ecma_loader = "0.42.5"
|
||||
swc_ecma_loader = { version = "0.42.5", path = "../../../rust/swc/crates/swc_ecma_loader", features = ["node", "cache"] }
|
||||
swc_ecma_ast = { version = "0.102.5", path = "../../../rust/swc/crates/swc_ecma_ast" }
|
||||
swc_atoms = { version = "0.4.43", path = "../../../rust/swc/crates/swc_atoms" }
|
||||
swc_ecma_parser = { version = "0.132.6", features = ["typescript"], path = "../../../rust/swc/crates/swc_ecma_parser" }
|
||||
swc_ecma_codegen = { version = "0.137.6", path = "../../../rust/swc/crates/swc_ecma_codegen" }
|
||||
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"
|
||||
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", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions" }
|
||||
jsx-dom-expressions = { version = "0.1", path = "../../../rust/swc-plugin-jsx-dom-expressions" }
|
||||
|
||||
|
|
|
|||
11
build/src/bundle_script.rs
Normal file
11
build/src/bundle_script.rs
Normal 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}")
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ impl Load for Loader {
|
|||
.fold_with(&mut strip(top_level_mark))
|
||||
.fold_with(&mut as_folder(TransformVisitor::new(
|
||||
jsx_dom_expressions::config::Config {
|
||||
module_name: "solid-js/web/dist/web.js".to_string(),
|
||||
module_name: "solid-js/web".to_string(),
|
||||
builtins: vec![
|
||||
"For".into(),
|
||||
"Show".into(),
|
||||
|
|
|
|||
38
script/api.ts
Normal file
38
script/api.ts
Normal 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[];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
import {render} from "solid-js/web/dist/web.js";
|
||||
import {render} from "solid-js/web";
|
||||
import {ready} from "./ready";
|
||||
import {FilterBar} from "./filterbar"
|
||||
import {Api} from "./api";
|
||||
|
||||
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)
|
||||
});
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export const FilterBar = ({name}) => {
|
||||
return <div>Hello {name}!</div>;
|
||||
}
|
||||
19
script/filterbar.tsx
Normal file
19
script/filterbar.tsx
Normal 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
19
src/data/maps.rs
Normal 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))
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod chat;
|
||||
pub mod demo;
|
||||
pub mod maps;
|
||||
pub mod player;
|
||||
pub mod steam_id;
|
||||
pub mod user;
|
||||
|
|
|
|||
11
src/main.rs
11
src/main.rs
|
|
@ -8,6 +8,7 @@ mod session;
|
|||
pub use crate::config::Config;
|
||||
use crate::config::Listen;
|
||||
use crate::data::demo::{Demo, ListDemo};
|
||||
use crate::data::maps::map_list;
|
||||
use crate::data::steam_id::SteamId;
|
||||
use crate::data::user::User;
|
||||
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> {
|
||||
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> {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
use crate::asset::saved_asset_url;
|
||||
use crate::data::demo::ListDemo;
|
||||
use crate::pages::Page;
|
||||
use maud::{html, Markup};
|
||||
use maud::{html, Markup, Render};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct Index {
|
||||
pub struct Index<'a> {
|
||||
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> {
|
||||
"Demos - demos.tf".into()
|
||||
}
|
||||
|
|
@ -17,7 +19,7 @@ impl Page for Index {
|
|||
let script = saved_asset_url!("demo_list.js");
|
||||
html! {
|
||||
h1 { "Demos" }
|
||||
.filter-bar {}
|
||||
#filter-bar data-maps = (MapList(&self.maps)) data-api-base = (self.api) {}
|
||||
table.demolist {
|
||||
thead {
|
||||
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
77
style/filterbar.css
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
@import '../filterbar.css';
|
||||
|
||||
.demolist {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue