Skip to content

Commit

Permalink
implement roulette v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Folleach committed Oct 13, 2024
1 parent 05402f7 commit b77c1a1
Show file tree
Hide file tree
Showing 31 changed files with 613 additions and 242 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

/static/images/
14 changes: 7 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ const App: React.FC = () => {
return <Router>
<LagnuageProvider>
<SettingsProvider>
<SearchProvider>
<Routes>
<Route path="/search" element={<SearchPage />}></Route>
<Route path="/roulette" element={<RouletteProvider><RoulettePage /></RouletteProvider>}></Route>
<Route path="/" element={<MainPage />}></Route>
</Routes>
</SearchProvider>
<SearchProvider>
<Routes>
<Route path="/search" element={<SearchPage />}></Route>
<Route path="/roulette" element={<RouletteProvider><RoulettePage /></RouletteProvider>}></Route>
<Route path="/" element={<MainPage />}></Route>
</Routes>
</SearchProvider>
</SettingsProvider>
</LagnuageProvider>
</Router>;
Expand Down
109 changes: 79 additions & 30 deletions src/components/AdvanceInput/AdvanceInput.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,95 @@
import React, { useEffect, useState } from "react";
import { Action } from "../../common/DotNetFeatures";
import { IFilterDefinition, QueryRequest } from "../../services/search/models";
import { AutocompleteResult, IFilterDefinition, QueryRequest } from "../../services/search/models";
import { useSearch } from "../../context/SearchContext";
import { TextField } from "../TextField";
import { buildQuery, parseQuery, parseRawQuery, toRaw } from "../../services/search/QueryBuilder";
import { parseRawQuery, readable, toRaw, toRawFilter, toRawFilters } from "../../services/search/QueryBuilder";
import './advanceInput.css';
import SelectableItem from "../SelectableItem/SelectableItem";
import FilterView from "./FilterView";

export interface IAdvanceInputParameters {
prepend?: QueryRequest,
submit: Action<QueryRequest>
submit: (value: QueryRequest) => void
changed?: (value: QueryRequest) => void
}

// grammar: (type + operator + value)* text
// length: 10
// length > 10 ролововрвововоароар

export default function AdvanceInput(p: IAdvanceInputParameters) {
const [filters, setFilters] = useState<IFilterDefinition[] | undefined>();
const [completions, setCompletions] = useState<AutocompleteResult>();
const [focused, setFocused] = useState<boolean>(false);
const state = p.prepend ?? { f: [] }
const search = useSearch();
const [state, setState] = useState(p.prepend ? toRaw(p.prepend) : "");

useEffect(() => {
search
.findFilters()
.then(x => setFilters(x))
.catch(e => console.error("failed to fetch builtin filters", e));
}, []);
.autocomplete(state.t ?? "")
.then(x => setCompletions(x))
.catch(e => {
console.error("failed to fetch completions", e);
setCompletions({ base: "", additional: [] });
});
}, [state]);

function componentKeyProcess(e: React.KeyboardEvent<HTMLDivElement>) {
if (e.key === "Escape") {
setFocused(false);
}
}

let input: HTMLInputElement | null = null;
return <div style={{ position: "relative" }} onKeyDown={componentKeyProcess}>
<div className={`shadowed transitioned rounded hlop ec inputCursor border`} onClick={() => input?.focus()}>
<div style={{ margin: "6px 4px", display: "flex" }}>
{state && state.f.map(x => <FilterView filter={x} />)}
<span>
<input
type="text"
ref={x => input = x}
onFocus={x => setFocused(true)}
value={state?.t ? state.t : ""}
onChange={event => {
const rawFilters = state ? toRawFilters(state) : "";
const query = parseRawQuery((rawFilters ? `${rawFilters} ` : "") + event.target.value)
if (p.changed) {
p.changed(query)
}
if (!focused)
setFocused(true);
}}
onKeyDown={e => {
if (e.key === 'Enter')
{
const rawFilters = state ? toRawFilters(state) : "";
const query = parseRawQuery((rawFilters ? `${rawFilters} ` : "") + (state.t ?? "") + (state.t?.lastIndexOf(" ") === state.t?.length ? " " : ""));
if (p.changed)
p.changed(query);
p.submit(query);
}
if (e.key === 'Backspace' && (state.t?.length ?? 0) == 0 && state.f.length > 0) {
const filter = state.f.pop();
if (!filter || !p.changed)
return;
p.changed({
f: state.f,
t: toRawFilter(filter)
});
}
}}></input>
</span>
</div>

let input: HTMLInputElement | null;
return <div className={`shadowed transitioned rounded hlop ec inputCursor border`} onClick={() => input?.focus()}>
<div style={{margin: "6px 4px"}}>
<input
type="text"
ref={x => input = x}
value={state}
onChange={event => {
setState(event.target.value)
}}
onKeyDown={e => {
if (e.key === 'Enter')
p.submit(parseRawQuery(state));
}}></input>
{(completions?.additional?.length ?? 0) > 0 && focused &&
<div style={{ position: "absolute", width: "100%", zIndex: 3 }}>
<div className="plate ppad">
{completions?.additional?.map(x => <SelectableItem action={() => {
if (p.changed) {
const rawFilters = state ? toRawFilters(state) : "";
const query = parseRawQuery((rawFilters ? `${rawFilters} ` : "") + `${state.t ?? ""}${x} `);
p.changed(query);
}
input?.focus();
}}><span style={{ color: "#8c9399" }}>{completions.base}</span><span>{x}</span></SelectableItem>)}
</div>
</div>
}
</div>
{/* {filters?.map(x => <p>{x.name}-{x.type}</p>)} */}
</div>;
}
16 changes: 16 additions & 0 deletions src/components/AdvanceInput/FilterView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { Filter } from '../../services/search/models';
import { readable } from '../../services/search/QueryBuilder';
import './advanceInput.css';

export interface IFilterViewProps {
filter: Filter
}

export default function FilterView(p: IFilterViewProps) {
return <span className="filter-highlight" style={{ marginRight: "0.5em", alignContent: "center" }}>
<span>{p.filter.n}</span>
<span> {readable(p.filter.o)}</span>
<span> {p.filter.v}</span>
</span>
}
15 changes: 15 additions & 0 deletions src/components/AdvanceInput/RequestView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { QueryRequest } from "../../services/search/models";
import FilterView from "./FilterView";
import './advanceInput.css';

export interface IRequestViewProps {
request: QueryRequest
}

export default function RequestView(p: IRequestViewProps) {
return <div style={{ margin: "0.5em 0" }}>
{p.request.f && p.request.f.map(x => <FilterView filter={x} />)}
{p.request.t && <span>{p.request.t}</span>}
</div>
}
20 changes: 20 additions & 0 deletions src/components/AdvanceInput/advanceInput.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.filter-highlight {
background: #e2f4ff;
color: #121d28;
border-radius: 0.5em;
padding: 0.5em 0.7em;
}

input span {
outline: none;
border: none;
background: none;
min-height: 36px;
align-items: center;
font: inherit;
width: 100%;
padding: 0 12px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
72 changes: 42 additions & 30 deletions src/components/Roulette/ChallengeSettingsComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,60 @@
import React, {useEffect, useState} from "react";
import { IDemonWeights } from "../../services/roulette/models";
import React, { useEffect, useState } from "react";
import { get } from "../../server/Backbone";
import { Plate, PlateStyle } from "../Plate";
import Text, { TextStyle } from "../Text/Text";
import { RouletteSlider } from "./RouletteSlider";

interface IRouletteBalanceResponse {
weights: IDemonWeights
}
import { IBalance, IRouletteLevelWeights } from "../../context/RouletteContext";
import { QueryRequest } from "../../services/search/models";
import { buildQuery } from "../../services/search/QueryBuilder";

export interface IRouletteBalanceProps {
onChange: (value: IDemonWeights) => void
request: QueryRequest
onChange: (value: IBalance) => void
}

export const ChallengeSettingsComponent: React.FC<IRouletteBalanceProps> = p => {
const [weights, setWeights] = useState<IDemonWeights>();
const [balance, setBalance] = useState<IBalance>({total: 0, weights: {}});

useEffect(() => {
get<IRouletteBalanceResponse>("roulette/balance").then(result => {
if (result?.weights)
setWeights(result.weights);
}).catch(e => console.log("error while fetching roulette balance"));
}, []);
get<IBalance>("roulette/v2/balance?" + new URLSearchParams({ query: buildQuery(p.request) }))
.then(result => {
if (result)
setBalance(result);
console.log("fetched", result);
})
.catch(e => console.log("error while fetching roulette balance"));
}, [p.request]);

useEffect(() => {
if (weights)
p.onChange(weights);
}, [weights])
if (balance)
p.onChange(balance);
}, [balance])

function render(current: number | undefined, icon: string, update: (v: number, weight: IRouletteLevelWeights) => IRouletteLevelWeights) {
const filter: string | undefined = current != undefined ? undefined : "grayscale(0.9)";
return <div style={{filter}}>
<RouletteSlider init={current} icon={icon} onChange={e => setBalance(prev => ({ ...prev!, weights: update(e, prev.weights) }))} />
</div>
}

return <Plate style={PlateStyle.Simple}>
{weights && <div>
<Text style={TextStyle.Default}>Use the sliders below to adjust the likelihood of encountering different
types of demons</Text>
<RouletteSlider init={weights.easyDemon} icon={`/images/difficulty_demon_easy.png`}
onChange={e => setWeights(prev => ({...prev!, easyDemon: e}))}/>
<RouletteSlider init={weights.mediumDemon} icon={`/images/difficulty_demon_normal.png`}
onChange={e => setWeights(prev => ({...prev!, mediumDemon: e}))}/>
<RouletteSlider init={weights.hardDemon} icon={`/images/difficulty_demon_hard.png`}
onChange={e => setWeights(prev => ({...prev!, hardDemon: e}))}/>
<RouletteSlider init={weights.insaneDemon} icon={`/images/difficulty_demon_harder.png`}
onChange={e => setWeights(prev => ({...prev!, insaneDemon: e}))}/>
<RouletteSlider init={weights.extremeDemon} icon={`/images/difficulty_demon_insane.png`}
onChange={e => setWeights(prev => ({...prev!, extremeDemon: e}))}/>
{balance && <div>
<Text style={TextStyle.Default}>Use the sliders below to adjust the likelihood of encountering different types of demons</Text>
{render(balance.weights.auto, `/images/difficulty_auto.png`, (e, prev) => ({ ...prev, auto: e }))}
{render(balance.weights.undef, `/images/difficulty_undefined.png`, (e, prev) => ({ ...prev!, undef: e }))}

{render(balance.weights.easy, `/images/difficulty_easy.png`, (e, prev) => ({ ...prev!, easy: e }))}
{render(balance.weights.normal, `/images/difficulty_normal.png`, (e, prev) => ({ ...prev!, normal: e }))}
{render(balance.weights.hard, `/images/difficulty_hard.png`, (e, prev) => ({ ...prev!, hard: e }))}
{render(balance.weights.harder, `/images/difficulty_harder.png`, (e, prev) => ({ ...prev!, harder: e }))}
{render(balance.weights.insane, `/images/difficulty_insane.png`, (e, prev) => ({ ...prev!, insane: e }))}

{render(balance.weights.easyDemon, `/images/difficulty_demon_easy.png`, (e, prev) => ({ ...prev!, easyDemon: e }))}
{render(balance.weights.mediumDemon, `/images/difficulty_demon_normal.png`, (e, prev) => ({ ...prev!, mediumDemon: e }))}
{render(balance.weights.hardDemon, `/images/difficulty_demon_hard.png`, (e, prev) => ({ ...prev!, hardDemon: e }))}
{render(balance.weights.insaneDemon, `/images/difficulty_demon_harder.png`, (e, prev) => ({ ...prev!, insaneDemon: e }))}
{render(balance.weights.extremeDemon, `/images/difficulty_demon_insane.png`, (e, prev) => ({ ...prev!, extremeDemon: e }))}
</div>}
{!weights && <Text style={TextStyle.Additional}>Loading...</Text>}
{!balance && <Text style={TextStyle.Additional}>Loading...</Text>}
</Plate>
}
10 changes: 5 additions & 5 deletions src/components/Roulette/Roulette.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { CSSProperties } from 'react';
import React, { CSSProperties, useEffect, useState } from 'react';
import { HighlightedString } from '../../services/models';

export interface IRouletteSegment {
Expand All @@ -23,15 +23,15 @@ const Roulette: React.FC<IRouletteProps> = p => {
const randSeconds = 5 + Math.random() * 3;
const rand90 = Math.floor(Math.random() * 90);
const t = `translate(120, 120) rotate(${rand360}deg)`;
console.log(t)
const style: CSSProperties = {
transform: `translate(1200px, 1200px) rotate(${rand360}deg)`,
animation: 'roulette_spin',
animationDelay: '1s',
animationDuration: `${randSeconds}s`,
animationFillMode: 'forwards'
};
const content = <svg xmlns="http://www.w3.org/2000/svg" width={p.radius * 2} height={p.radius * 2} viewBox="0 0 2400 2400">

const content = <svg key={p.segments[0].text?.items[0].value} xmlns="http://www.w3.org/2000/svg" width={p.radius * 2} height={p.radius * 2} viewBox="0 0 2400 2400">
<style>
{
`@keyframes roulette_spin {
Expand All @@ -46,7 +46,7 @@ const Roulette: React.FC<IRouletteProps> = p => {
</style>
<style>
{
`.flame-gradient {
`.flame-gradient {
fill: url(#flame)
}
Expand All @@ -59,7 +59,7 @@ const Roulette: React.FC<IRouletteProps> = p => {
<defs>
<radialGradient id="flame" >
<stop offset="0%" stop-color="#ffc982" />
<stop offset="80%" stop-color="#fdd39d"/>
<stop offset="80%" stop-color="#fdd39d" />
<stop offset="95%" stop-color="#ffefd8" />
<stop offset="100%" stop-color="#ffffff" />
</radialGradient>
Expand Down
23 changes: 20 additions & 3 deletions src/components/Roulette/RouletteItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { useRoulette } from '../../context/RouletteContext';
import { Plate } from '../Plate';
import Text, { TextStyle } from '../Text/Text';
import { IRoulettePreview } from '../../services/roulette/models';
import FilterView from '../AdvanceInput/FilterView';
import RequestView from '../AdvanceInput/RequestView';
import WeightsView from '../Weights/WeightsView';

interface IRouletteItemProps {
roulette: IRoulettePreview
Expand All @@ -25,10 +28,24 @@ export const RouletteItem: React.FC<IRouletteItemProps> = (p) => {
<Text style={TextStyle.MainHeader}>{p.roulette.name ?? "unnamed"}</Text>
</div>

<div className={"ec"}>
<Text style={TextStyle.Additional}>{p.roulette.type === "challenge" ? "Challenge" : "Normal"}</Text>
{!p.roulette.request &&
<div className={"ec"}>
<Text style={TextStyle.Additional}>{p.roulette.type === "challenge" ? "Challenge" : "Normal"}</Text>
</div>
}
{p.roulette.request &&
<div style={{marginTop: "1em"}}>
<RequestView request={p.roulette.request} />
</div>
}
{p.roulette.weights &&
<div style={{marginTop: "1em"}}>
<WeightsView weights={p.roulette.weights} />
</div>
}
<div style={{marginTop: "1em"}}>
<Text style={TextStyle.Additional}>{new Date(p.roulette.createDt).toLocaleString()}</Text>
</div>
<Text style={TextStyle.Additional}>{new Date(p.roulette.createDt).toLocaleString()}</Text>
</Plate>
</div>
}
Loading

0 comments on commit b77c1a1

Please sign in to comment.