mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
improve sync ui
This commit is contained in:
parent
b08267a08c
commit
ed63ffa394
8 changed files with 129 additions and 51 deletions
|
|
@ -3,6 +3,8 @@ export interface AnalyseMenuProps {
|
|||
onShare: Function;
|
||||
canShare: boolean;
|
||||
isShared: boolean;
|
||||
clients: number,
|
||||
inShared: boolean,
|
||||
}
|
||||
|
||||
export function AnalyseMenu(props: AnalyseMenuProps) {
|
||||
|
|
@ -13,17 +15,34 @@ export function AnalyseMenu(props: AnalyseMenuProps) {
|
|||
style={{width: `${(loc().length * 8)}px`}}
|
||||
onFocus={(event) => {
|
||||
(event.target as HTMLInputElement).select()
|
||||
}}/> : '';
|
||||
}}/> : <span class="share-text">Start a shared session</span>;
|
||||
|
||||
const shareButton = () => (props.canShare) ? <div class="analyse-menu">
|
||||
const clientCount = () => (props.isShared) ?
|
||||
<div class="clients">{props.clients} {(props.clients === 1) ? "spectator" : "spectators"}</div> : [];
|
||||
|
||||
const shareButton = () => {
|
||||
if (props.canShare) {
|
||||
|
||||
return [
|
||||
<div class="share">
|
||||
<button class="share-session" title="Start a shared session"
|
||||
onClick={() => {
|
||||
props.onShare()
|
||||
}}/>
|
||||
{shareText}
|
||||
</div> : '';
|
||||
</div>,
|
||||
clientCount,
|
||||
]
|
||||
} else if (props.inShared) {
|
||||
return <div class="share shared">
|
||||
You're spectating a session controlled by someone else
|
||||
</div>
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return (<div>
|
||||
return (<div class="analyse-menu">
|
||||
{shareButton}
|
||||
</div>)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,13 +25,14 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
const [scale, setScale] = createSignal<number>(1);
|
||||
const [playing, setPlaying] = createSignal<boolean>(false);
|
||||
const [sessionName, setSessionName] = createSignal<string>("");
|
||||
const [clients, setClients] = createSignal<number>(0);
|
||||
|
||||
let lastFrameTime = 0;
|
||||
let playStartTick = 0;
|
||||
let playStartTime = 0;
|
||||
|
||||
const onUpdate = (update: StateUpdate) => {
|
||||
if (update["tick"]) {
|
||||
if (update.hasOwnProperty("tick")) {
|
||||
setTick(update["tick"]);
|
||||
}
|
||||
if (update.hasOwnProperty("playing")) {
|
||||
|
|
@ -41,6 +42,10 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
pause();
|
||||
}
|
||||
}
|
||||
if (update.hasOwnProperty("clients")) {
|
||||
console.log(update["clients"]);
|
||||
setClients(update["clients"]);
|
||||
}
|
||||
}
|
||||
|
||||
let session: Session | null = null;
|
||||
|
|
@ -137,7 +142,7 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
const buildings = () => parser.getBuildingsAtTick(tick());
|
||||
const kills = parser.getKills();
|
||||
const playButtonText = () => (playing()) ? '⏸' : '▶️';
|
||||
const disabled = session && !session.isOwner();
|
||||
const inShared = session && !session.isOwner();
|
||||
const isShared = () => sessionName() !== '';
|
||||
|
||||
console.log(intervalPerTick);
|
||||
|
|
@ -160,12 +165,15 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
onShare={() => {
|
||||
session = Session.create({
|
||||
tick: tick(),
|
||||
playing: playing()
|
||||
});
|
||||
playing: playing(),
|
||||
clients: 0,
|
||||
}, onUpdate);
|
||||
setSessionName(session.sessionName);
|
||||
}}
|
||||
canShare={props.isStored && !disabled}
|
||||
canShare={props.isStored && !inShared}
|
||||
isShared={isShared()}
|
||||
clients={clients()}
|
||||
inShared={inShared}
|
||||
/>
|
||||
<SpecHUD parser={parser} tick={tick()}
|
||||
players={players()} kills={kills}/>
|
||||
|
|
@ -174,14 +182,14 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
title={timeTitle()}>
|
||||
<input class="play-pause-button" type="button"
|
||||
value={playButtonText()}
|
||||
disabled={disabled}
|
||||
disabled={inShared}
|
||||
onClick={togglePlay}
|
||||
/>
|
||||
<Timeline parser={parser} tick={tick()}
|
||||
onSetTick={throttle(50, (tick) => {
|
||||
setTickNow(tick);
|
||||
})}
|
||||
disabled={disabled}/>
|
||||
disabled={inShared}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,24 +1,22 @@
|
|||
import {ownKeys} from "solid-js/store/types/store";
|
||||
|
||||
const syncUri = 'wss://sync.demos.tf';
|
||||
|
||||
export class Session {
|
||||
public readonly owner_token: string | null;
|
||||
private socket: WebSocket | null;
|
||||
public readonly sessionName: string;
|
||||
private initialState: PlaybackState | null;
|
||||
private readonly onState: (StateUpdate) => void | null;
|
||||
private readonly syncUri;
|
||||
|
||||
constructor(name: string, owner_token: string | null = null, initialState: PlaybackState | null, onState: (StateUpdate) => void | null = null) {
|
||||
constructor(name: string, owner_token: string | null = null, initialState: PlaybackState | null, onState: (StateUpdate) => void) {
|
||||
this.owner_token = owner_token;
|
||||
this.sessionName = name;
|
||||
this.initialState = initialState;
|
||||
this.onState = onState;
|
||||
this.syncUri = document.querySelector('[data-sync]').getAttribute('data-sync');
|
||||
this.open();
|
||||
}
|
||||
|
||||
public static create(state: PlaybackState): Session {
|
||||
return new Session(generateToken(), generateToken(), state)
|
||||
public static create(state: PlaybackState, onState: (StateUpdate) => void): Session {
|
||||
return new Session(generateToken(), generateToken(), state, onState)
|
||||
}
|
||||
|
||||
public static join(name: string, onState: (StateUpdate) => void): Session {
|
||||
|
|
@ -29,10 +27,11 @@ export class Session {
|
|||
if (this.socket) {
|
||||
return;
|
||||
}
|
||||
this.socket = new WebSocket(syncUri);
|
||||
this.socket = new WebSocket(this.syncUri);
|
||||
this.socket.onopen = () => {
|
||||
if (this.socket) {
|
||||
if (this.owner_token) {
|
||||
if (this.initialState) {
|
||||
this.socket.send(JSON.stringify({
|
||||
type: 'create',
|
||||
session: this.sessionName,
|
||||
|
|
@ -49,6 +48,15 @@ export class Session {
|
|||
play: this.initialState.playing
|
||||
}));
|
||||
this.initialState = null;
|
||||
}
|
||||
this.socket.onmessage = (event) => {
|
||||
const packet = JSON.parse(event.data) as Packet;
|
||||
if (packet.type === 'clients') {
|
||||
this.onState({
|
||||
clients: packet.count
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.socket.send(JSON.stringify({
|
||||
type: 'join',
|
||||
|
|
@ -62,7 +70,7 @@ export class Session {
|
|||
});
|
||||
}
|
||||
if (packet.type === 'play') {
|
||||
if (packet.play || packet.tick) {
|
||||
if (packet.play) {
|
||||
this.onState({
|
||||
playing: true
|
||||
});
|
||||
|
|
@ -123,6 +131,7 @@ function generateToken(): string {
|
|||
export interface PlaybackState {
|
||||
tick: number,
|
||||
playing: boolean,
|
||||
clients: number,
|
||||
}
|
||||
|
||||
export type StateUpdate = Partial<PlaybackState>;
|
||||
|
|
@ -147,7 +156,12 @@ export interface PlayPacket {
|
|||
type: 'play';
|
||||
session: string;
|
||||
play?: boolean;
|
||||
tick?: boolean; //old sync server
|
||||
}
|
||||
|
||||
export type Packet = JoinPacket | CreatePacket | TickPacket | PlayPacket;
|
||||
export interface ClientsPacket {
|
||||
type: 'clients';
|
||||
session: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export type Packet = JoinPacket | CreatePacket | TickPacket | PlayPacket | ClientsPacket;
|
||||
|
|
@ -86,6 +86,8 @@ pub struct SiteConfig {
|
|||
pub api: String,
|
||||
#[serde(default = "default_maps")]
|
||||
pub maps: String,
|
||||
#[serde(default = "default_sync")]
|
||||
pub sync: String,
|
||||
}
|
||||
|
||||
fn default_api() -> String {
|
||||
|
|
@ -96,6 +98,10 @@ fn default_maps() -> String {
|
|||
"https://maps.demos.tf/".into()
|
||||
}
|
||||
|
||||
fn default_sync() -> String {
|
||||
"wss://sync.demos.tf/".into()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TracingConfig {
|
||||
pub endpoint: String,
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ struct App {
|
|||
openid: SteamOpenId,
|
||||
api: String,
|
||||
maps: String,
|
||||
sync: String,
|
||||
map_list: MapList,
|
||||
pub session_store: MemoryStore,
|
||||
}
|
||||
|
|
@ -156,6 +157,7 @@ async fn main() -> Result<()> {
|
|||
.expect("invalid steam login url"),
|
||||
api: config.site.api,
|
||||
maps: config.site.maps,
|
||||
sync: config.site.sync,
|
||||
map_list,
|
||||
session_store: session_store.clone(),
|
||||
});
|
||||
|
|
@ -518,6 +520,7 @@ async fn viewer(
|
|||
ViewerPage {
|
||||
demo,
|
||||
maps: &app.maps,
|
||||
sync: &app.sync,
|
||||
},
|
||||
session,
|
||||
))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use std::borrow::Cow;
|
|||
pub struct ViewerPage<'a> {
|
||||
pub demo: Option<Demo>,
|
||||
pub maps: &'a str,
|
||||
pub sync: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Asset)]
|
||||
|
|
@ -48,8 +49,9 @@ impl Page for ViewerPage<'_> {
|
|||
let script = ViewerScript::url();
|
||||
let style_url = ViewerStyle::url();
|
||||
let maps = self.maps;
|
||||
let sync = self.sync;
|
||||
html! {
|
||||
.viewer-page data-maps = (maps) {
|
||||
.viewer-page data-maps = (maps) data-sync = (sync) {
|
||||
@if let Some(demo) = self.demo.as_ref() {
|
||||
input type = "hidden" name = "url" value = (demo.url) {}
|
||||
progress.download min = "0" max = "100" value = "0" {}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,31 @@
|
|||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0.5;
|
||||
transition: all 0.5s;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
& .share {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.shared {
|
||||
padding: 2px 2px 2px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
|
||||
& span.share-text {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
& .share-session {
|
||||
|
||||
background: transparent;
|
||||
color: var(--primary-color);
|
||||
font-size: 200%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 10px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
|
@ -27,7 +39,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
& .share-text {
|
||||
& input.share-text {
|
||||
color: var(--primary-color);
|
||||
background-color: var(--text-secondary);
|
||||
padding: 5px;
|
||||
|
|
@ -39,4 +51,18 @@
|
|||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
span.share-text {
|
||||
color: var(--primary-color);
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s;
|
||||
|
||||
&:active, &:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.clients {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
.map-holder {
|
||||
position: fixed;
|
||||
top: 32px;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 32px - 100px);
|
||||
height: calc(100% - 40px - 100px);
|
||||
}
|
||||
|
||||
.time-control {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue