Compare commits

..

19 Commits

Author SHA1 Message Date
c3fc993f5a added ";" for convar 2021-11-16 02:39:40 +01:00
64f1ea0be6 added fromObject method 2021-11-16 02:16:37 +01:00
9e7b456400 implemented save 2021-11-16 01:13:59 +01:00
7cc9fd7722 copy to clipboard 2021-11-16 01:04:59 +01:00
c036d7d5ca export modal 2021-11-16 00:58:56 +01:00
bd271021f1 eslint fix 2021-11-15 23:36:57 +01:00
63b1538a37 something broke with eslint. fixed it 2021-11-15 23:35:56 +01:00
b0b6e7084e eslint cleanup 2021-11-15 23:31:09 +01:00
35475e36db added eslint 2021-11-15 23:31:02 +01:00
a743eca07f export to json 2021-11-15 23:23:02 +01:00
e450020cda kinda implemented export 2021-11-15 22:31:00 +01:00
d095488c74 elevated state to App 2021-11-15 22:15:38 +01:00
4273b5c8ca added menu bar 2021-11-15 22:07:41 +01:00
cfbb358115 fixed side for bonus cards 2021-11-15 19:31:15 +01:00
39df588c7a AllDecks enemy & bonus cards 2021-11-15 19:30:49 +01:00
ba61a5258c defuser count padding 2021-11-15 19:24:12 +01:00
3412e78237 deck scroll 2021-11-15 19:21:44 +01:00
a913d2d5cb lower card hight 2021-11-15 19:20:08 +01:00
49fb1d90a0 a bit smart way 2021-11-15 19:18:43 +01:00
21 changed files with 2532 additions and 2338 deletions

30
.eslintrc.yml Normal file
View File

@@ -0,0 +1,30 @@
env:
browser: true
es2021: true
extends:
- 'eslint:recommended'
- 'plugin:react/recommended'
- 'plugin:@typescript-eslint/recommended'
parser: '@typescript-eslint/parser'
parserOptions:
ecmaFeatures:
jsx: true
ecmaVersion: 12
sourceType: module
plugins:
- react
- '@typescript-eslint'
rules:
indent:
- error
- tab
linebreak-style:
- error
- unix
quotes:
- error
- double
semi:
- error
- never
react/prop-types: 0

View File

@@ -9,6 +9,7 @@
"@types/react": "^17.0.0", "@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0", "@types/react-dom": "^17.0.0",
"autoprefixer": "^9", "autoprefixer": "^9",
"copy-to-clipboard": "^3.3.1",
"postcss": "^7", "postcss": "^7",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
@@ -39,5 +40,10 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-plugin-react": "^7.27.0"
} }
} }

View File

@@ -1,33 +1,50 @@
import React, { useState } from "react" import React from "react"
import RetakesConfig from "../types/RetakesConfig" import RetakesConfig from "../types/RetakesConfig"
import Side from "../types/Side" import Side from "../types/Side"
import CardComp from "./Card"
import DeckComp from "./Deck" import DeckComp from "./Deck"
const AllDecks: React.FC = () => { interface Props {
const [retakesConfig,setRetakesConfig] = useState(new RetakesConfig()) retakesConfig: RetakesConfig
onChange?: (newConfig: RetakesConfig)=>void
}
const AllDecks: React.FC<Props> = ({retakesConfig,onChange}) => {
return ( return (
<div> <div>
<DeckComp title="CT Pistol" side={Side.CT} deck={retakesConfig.ctPistol} <DeckComp title="CT Pistol" side={Side.CT} deck={retakesConfig.ctPistol}
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{ctPistol:newDeck}}))} /> onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{ctPistol:newDeck}}))} />
<DeckComp title="T Pistol" side={Side.T} deck={retakesConfig.tPistol} <DeckComp title="T Pistol" side={Side.T} deck={retakesConfig.tPistol}
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{tPistol:newDeck}}))} /> onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{tPistol:newDeck}}))} />
<DeckComp title="CT Upgraded Pistol" side={Side.CT} deck={retakesConfig.ctUpgradedPistol} <DeckComp title="CT Upgraded Pistol" side={Side.CT} deck={retakesConfig.ctUpgradedPistol}
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{ctUpgradedPistol:newDeck}}))} /> onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{ctUpgradedPistol:newDeck}}))} />
<DeckComp title="T Upgraded Pistol" side={Side.T} deck={retakesConfig.tUpgradedPistol} <DeckComp title="T Upgraded Pistol" side={Side.T} deck={retakesConfig.tUpgradedPistol}
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{tUpgradedPistol:newDeck}}))} /> onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{tUpgradedPistol:newDeck}}))} />
<DeckComp title="CT Light" side={Side.CT} deck={retakesConfig.ctLight} <DeckComp title="CT Light" side={Side.CT} deck={retakesConfig.ctLight}
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{ctLight:newDeck}}))} /> onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{ctLight:newDeck}}))} />
<DeckComp title="T Light" side={Side.T} deck={retakesConfig.tLight} <DeckComp title="T Light" side={Side.T} deck={retakesConfig.tLight}
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{tLight:newDeck}}))} /> onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{tLight:newDeck}}))} />
<DeckComp title="CT Full" side={Side.CT} deck={retakesConfig.ctFull} <DeckComp title="CT Full" side={Side.CT} deck={retakesConfig.ctFull}
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{ctFull:newDeck}}))} /> onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{ctFull:newDeck}}))} />
<DeckComp title="T Full" side={Side.T} deck={retakesConfig.tFull} <DeckComp title="T Full" side={Side.T} deck={retakesConfig.tFull}
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{tFull:newDeck}}))} /> onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{tFull:newDeck}}))} />
<div className="flex">
<CardComp card={retakesConfig.ctEnemy} side={Side.T}
onChange={(newCard)=>onChange?.(new RetakesConfig({...retakesConfig,...{ctEnemy:newCard}}))} />
<CardComp card={retakesConfig.tEnemy} side={Side.CT}
onChange={(newCard)=>onChange?.(new RetakesConfig({...retakesConfig,...{tEnemy:newCard}}))} />
</div>
<div className="flex">
<CardComp card={retakesConfig.ctBonus} side={Side.CT}
onChange={(newCard)=>onChange?.(new RetakesConfig({...retakesConfig,...{ctBonus:newCard}}))} />
<CardComp card={retakesConfig.tBonus} side={Side.T}
onChange={(newCard)=>onChange?.(new RetakesConfig({...retakesConfig,...{tBonus:newCard}}))} />
</div>
</div> </div>
) )
} }

View File

@@ -1,22 +1,63 @@
import React from 'react'; import React, { useEffect, useState } from "react"
import AllDecks from './AllDecks'; import copy from "copy-to-clipboard"
import RetakesConfig from "../types/RetakesConfig"
import AllDecks from "./AllDecks"
import MenuBar from "./MenuBar"
import Modal from "./Modal"
function App() { const App: React.FC = () => {
// const [CTDeck,setCTDeck] = useState<Deck>(new Deck( const [retakesConfig,setRetakesConfig] = useState(new RetakesConfig())
// 2,new CardGroup( const [showExport,setShowExport] = useState(false)
// 2,new Card("AWE fake",false,false,Item.AWP,Item.DECOY), const [exportText,setExportText] = useState("")
// new Card("M4 Flash",true,true,Item.AK_47_M4,Item.FLASHBANG)
// ), useEffect(()=>{
// new CardGroup( // Load saved config
// 3,new Card("One deag",true,false,Item.DESERT_EAGLE) const retakesJSON = window.localStorage.getItem("retakesJSON")
// ) if (retakesJSON){
// )) try{
const parsedConfig: Record<string,unknown> = JSON.parse(retakesJSON)
setRetakesConfig(RetakesConfig.fromObject(parsedConfig))
}catch(err){
window.localStorage.removeItem("retakesJSON")
}
}
},[])
return ( return (
<div className="text-white"> <div className="text-white">
<AllDecks /> <MenuBar
onExport={()=>{
setExportText(retakesConfig.toCvar())
setShowExport(true)
}}
onExportJson={()=>{
setExportText(JSON.stringify(retakesConfig))
setShowExport(true)
}}
onSave={()=>{
const jsonString = JSON.stringify(retakesConfig)
window.localStorage.setItem("retakesJSON",jsonString)
// TODO: user feedback that config was saved
}}
/>
<AllDecks retakesConfig={retakesConfig} onChange={(newConfig)=>setRetakesConfig(newConfig)} />
<Modal show={showExport} onCloseClick={()=>setShowExport(false)}>
<div className="flex justify-center mb-2">
<textarea
cols={50} rows={10}
value={exportText}
readOnly
className="bg-transparent border-2 border-gray-900"
/>
</div> </div>
); <div
className="bg-gray-700 button flex justify-center"
onClick={()=>copy(exportText)}
>Copy to clipboard</div>
</Modal>
</div>
)
} }
export default App; export default App

View File

@@ -15,7 +15,7 @@ interface Props {
const CardComp: React.FC<Props> = ({card,side,onChange,onRemove}) => { const CardComp: React.FC<Props> = ({card,side,onChange,onRemove}) => {
return ( return (
<div className="bg-gray-600 m-1 p-1 w-72 min-h-12"> <div className="bg-gray-600 m-1 p-1 w-72 min-h-card">
<span className="float-right cursor-pointer" onClick={()=>{onRemove?.()}}>X</span> <span className="float-right cursor-pointer" onClick={()=>{onRemove?.()}}>X</span>
<span className="font-bold"><TextEdit <span className="font-bold"><TextEdit
text={card.title} text={card.title}

View File

@@ -38,7 +38,7 @@ const CardGroupComp: React.FC<Props> = ({cardGroup,side,onChange,onRemove}) => {
/> />
)} )}
<div <div
className="bg-gray-600 m-1 p-1 cursor-pointer my-auto" className="bg-gray-600 my-auto button"
onClick={()=>onChange?.( onClick={()=>onChange?.(
new CardGroup( new CardGroup(
cardGroup.numInDeck, cardGroup.numInDeck,

View File

@@ -15,10 +15,10 @@ interface Props {
const DeckComp: React.FC<Props> = ({deck,side,title,onChange}) => { const DeckComp: React.FC<Props> = ({deck,side,title,onChange}) => {
return ( return (
<div className="bg-gray-800 m-1 p-1"> <div className="bg-gray-800 m-1 p-1 overflow-x-scroll">
<span className="font-bold">{title}</span> <span className="font-bold">{title}</span>
{ side === Side.CT && <span> { side === Side.CT && <span className="px-2">
D: <NumEdit Defuser: <NumEdit
value={deck.numDefusers} value={deck.numDefusers}
onChange={newNum => onChange?.(new Deck(Math.max(newNum,0),...deck.cardGroups))} onChange={newNum => onChange?.(new Deck(Math.max(newNum,0),...deck.cardGroups))}
/> </span> } /> </span> }
@@ -42,7 +42,7 @@ const DeckComp: React.FC<Props> = ({deck,side,title,onChange}) => {
}} }}
/> />
)} )}
<div className="bg-gray-700 m-1 p-1 cursor-pointer my-auto" <div className="bg-gray-700 button my-auto"
onClick={()=>onChange?.( onClick={()=>onChange?.(
new Deck( new Deck(
deck.numDefusers, deck.numDefusers,

View File

@@ -0,0 +1,30 @@
import React from "react"
interface Props {
onExport?: ()=>void
onExportJson?: ()=>void
onSave?:()=>void
}
const MenuBar: React.FC<Props> = ({onExport,onExportJson,onSave}) => {
return (
<div className="bg-gray-800 h-10 m-1 p-1 flex">
<div className="bg-gray-700 button" onClick={onExport}>
Export
</div>
<div className="bg-gray-700 button" onClick={onExportJson}>
Export to JSON
</div>
<div className="bg-gray-700 button" onClick={onSave}>
Save
</div>
<a href="https://developer.valvesoftware.com/wiki/CS:GO_Game_Mode_-_Retakes" target="_blank" rel='noreferrer'>
<div className="bg-gray-700 button">
Help
</div>
</a>
</div>
)
}
export default MenuBar

26
src/components/Modal.tsx Normal file
View File

@@ -0,0 +1,26 @@
import React from "react"
interface Props {
show: boolean
onCloseClick?: ()=>void
}
const Modal: React.FC<Props> = ({children,show,onCloseClick}) => {
return (
<div
className={`${!show?"hidden":"" }
fixed z-10 left-0 top-0 w-full h-full
flex justify-center items-center bg-gray-900 bg-opacity-80`}
onClick={()=>{
onCloseClick?.()
}}>
<div className="bg-gray-800 mx-auto p-5 border-2 border-gray-800 w-10/12 overflow-hidden max-h-full" onClick={(e)=>{
e.stopPropagation()
}}>
{children}
</div>
</div>
)
}
export default Modal

View File

@@ -25,10 +25,12 @@ const TextEdit: React.FC<Props> = ({text,onChange}) => {
<> <>
{!edit && <span className="cursor-pointer" onClick={()=>setEdit(true)} >{inputValue}</span>} {!edit && <span className="cursor-pointer" onClick={()=>setEdit(true)} >{inputValue}</span>}
{edit && {edit &&
<form className="inline" onSubmit={(e)=>{ <form className="inline"
onSubmit={(e)=>{
e.preventDefault() e.preventDefault()
setEdit(false) setEdit(false)
}} > }}
>
<input <input
type="text" type="text"
className="bg-transparent outline-none font-bold" className="bg-transparent outline-none font-bold"

View File

@@ -2,6 +2,10 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
.min-h-12 { .min-h-card {
min-height: 12rem; min-height: 10rem;
}
.button {
@apply py-1 px-2 mx-2 hover:bg-gray-900 cursor-pointer
} }

View File

@@ -1,11 +1,11 @@
import React from 'react'; import React from "react"
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom"
import App from './components/App'; import App from "./components/App"
import './index.css'; import "./index.css"
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById('root') document.getElementById("root")
); )

View File

@@ -13,9 +13,18 @@ class Card {
this.items = items this.items = items
} }
public toString(): string { public toCvar(): string {
return `${this.title},${bToS(this.armor)},${bToS(this.helmet)},${this.items.join(",")}` return `${this.title},${bToS(this.armor)},${bToS(this.helmet)},${this.items.join(",")}`
} }
static fromObject(input: Record<string, unknown>): Card {
return new Card(
input.title as string,
input.armor as boolean,
input.helmet as boolean,
...input.items as Item[]
)
}
} }
/** /**

View File

@@ -9,8 +9,15 @@ class CardGroup {
this.cards = cards this.cards = cards
} }
public toString(): string { public toCvar(): string {
return `${this.numInDeck};${this.cards.map(e => e.toString()).join(";")}` return `${this.numInDeck};${this.cards.map(e => e.toCvar()).join(";")}`
}
static fromObject(input: Record<string, unknown>): CardGroup{
return new CardGroup(
input.numInDeck as number,
...(input.cards as Record<string, unknown>[]).map((e)=>Card.fromObject(e))
)
} }
} }

View File

@@ -9,8 +9,15 @@ class Deck {
this.cardGroups = cardGroups this.cardGroups = cardGroups
} }
public toString(): string { public toCvar(): string {
return `${this.numDefusers}|${this.cardGroups.map(e => e.toString()).join("|")}` return `${this.numDefusers}|${this.cardGroups.map(e => e.toCvar()).join("|")}`
}
static fromObject(input: Record<string, unknown>): Deck{
return new Deck(
input.numDefusers as number,
...(input.cardGroups as Record<string, unknown>[]).map((e)=>CardGroup.fromObject(e))
)
} }
} }

View File

@@ -2,26 +2,27 @@ import Card from "./Card"
import Deck from "./Deck" import Deck from "./Deck"
class RetakesConfig { class RetakesConfig {
readonly ctPistol: Deck readonly ctPistol: Deck = new Deck(0)
readonly tPistol: Deck readonly tPistol: Deck = new Deck(0)
readonly ctUpgradedPistol: Deck readonly ctUpgradedPistol: Deck = new Deck(0)
readonly tUpgradedPistol: Deck readonly tUpgradedPistol: Deck = new Deck(0)
readonly ctLight: Deck readonly ctLight: Deck = new Deck(0)
readonly tLight: Deck readonly tLight: Deck = new Deck(0)
readonly ctFull: Deck readonly ctFull: Deck = new Deck(0)
readonly tFull: Deck readonly tFull: Deck = new Deck(0)
readonly ctEnemy: Card readonly ctEnemy: Card = new Card("CT Enemy card",false,false)
readonly tEnemy: Card readonly tEnemy: Card = new Card("T Enemy card",false,false)
readonly ctBonus: Card readonly ctBonus: Card = new Card("CT Bonus card",false,false)
readonly tBonus: Card readonly tBonus: Card = new Card("T Bonus card",false,false)
readonly ctBonusAvailability: number[] // If not set to [1] or a valid value the game will crash
readonly tBonusAvailability: number[] readonly ctBonusAvailability: number[] = [1]
readonly tBonusAvailability: number[] = [1]
// TODO: there must be some smarter way to do this // TODO: there must be some smarter way to do this
constructor(args? :{ constructor(args? :{
@@ -40,30 +41,58 @@ class RetakesConfig {
ctBonusAvailability?: number[], ctBonusAvailability?: number[],
tBonusAvailability?: number[], tBonusAvailability?: number[],
}){ }){
this.ctPistol = args?.ctPistol ?? new Deck(0) if (args){
this.tPistol = args?.tPistol ?? new Deck(0) args.ctPistol && (this.ctPistol = args.ctPistol)
args.tPistol && (this.tPistol = args.tPistol)
this.ctUpgradedPistol = args?.ctUpgradedPistol ?? new Deck(0) args.ctUpgradedPistol && (this.ctUpgradedPistol = args.ctUpgradedPistol)
this.tUpgradedPistol = args?.tUpgradedPistol ?? new Deck(0) args.tUpgradedPistol && (this.tUpgradedPistol = args.tUpgradedPistol)
args.ctLight && (this.ctLight = args.ctLight)
this.ctLight = args?.ctLight ?? new Deck(0) args.tLight && (this.tLight = args.tLight)
this.tLight = args?.tLight ?? new Deck(0) args.ctFull && (this.ctFull = args.ctFull)
args.tFull && (this.tFull = args.tFull)
this.ctFull = args?.ctFull ?? new Deck(0) args.ctEnemy && (this.ctEnemy = args.ctEnemy)
this.tFull = args?.tFull ?? new Deck(0) args.tEnemy && (this.tEnemy = args.tEnemy)
args.ctBonus && (this.ctBonus = args.ctBonus)
this.ctEnemy = args?.ctEnemy ?? new Card("CT Enemy card",false,false) args.tBonus && (this.tBonus = args.tBonus)
this.tEnemy = args?.tEnemy ?? new Card("T Enemy Card",false,false) args.ctBonusAvailability && (this.ctBonusAvailability = args.ctBonusAvailability)
args.tBonusAvailability && (this.tBonusAvailability = args.tBonusAvailability)
this.ctBonus = args?.ctBonus ?? new Card("CT Bonus card",false,false) }
this.tBonus = args?.tBonus ?? new Card("T Bonus card",false,false)
this.ctBonusAvailability = args?.ctBonusAvailability ?? [1] // Has to be 1 or the game will crash
this.tBonusAvailability = args?.tBonusAvailability ?? [1]
} }
public toCvar(){ public toCvar(): string{
console.debug("THIS:")
return `mp_retake_ct_loadout_default_pistol_round "${this.ctPistol.toCvar()}";
mp_retake_t_loadout_default_pistol_round "${this.tPistol.toCvar()}";
mp_retake_ct_loadout_upgraded_pistol_round "${this.ctUpgradedPistol.toCvar()}";
mp_retake_t_loadout_upgraded_pistol_round "${this.tUpgradedPistol.toCvar()}";
mp_retake_ct_loadout_light_buy_round "${this.ctLight.toCvar()}";
mp_retake_t_loadout_light_buy_round "${this.tLight.toCvar()}";
mp_retake_ct_loadout_full_buy_round "${this.ctFull.toCvar()}";
mp_retake_t_loadout_full_buy_round "${this.tFull.toCvar()}";
mp_retake_ct_loadout_bonus_card "${this.ctBonus.toCvar()}";
mp_retake_t_loadout_bonus_card "${this.tBonus.toCvar()}";
mp_retake_ct_loadout_bonus_card_availability "${this.ctBonusAvailability.join(",")}";
mp_retake_t_loadout_bonus_card_availability "${this.tBonusAvailability.join(",")}";
`
}
static fromObject(input: Record<string, unknown>): RetakesConfig {
return new RetakesConfig({
ctPistol: Deck.fromObject(input.ctPistol as Record<string, unknown>),
tPistol: Deck.fromObject(input.tPistol as Record<string,unknown>),
ctUpgradedPistol: Deck.fromObject(input.ctUpgradedPistol as Record<string,unknown>),
tUpgradedPistol: Deck.fromObject(input.tUpgradedPistol as Record<string,unknown>),
ctLight: Deck.fromObject(input.ctLight as Record<string,unknown>),
tLight: Deck.fromObject(input.tLight as Record<string,unknown>),
ctFull: Deck.fromObject(input.ctFull as Record<string,unknown>),
tFull: Deck.fromObject(input.tFull as Record<string,unknown>),
ctEnemy: Card.fromObject(input.ctEnemy as Record<string,unknown>),
tEnemy: Card.fromObject(input.tEnemy as Record<string,unknown>),
ctBonus: Card.fromObject(input.ctBonus as Record<string,unknown>),
tBonus: Card.fromObject(input.tBonus as Record<string,unknown>),
ctBonusAvailability: input.ctBonusAvailability as number[],
tBonusAvailability: input.tBonusAvailability as number[],
})
} }
} }

4036
yarn.lock

File diff suppressed because it is too large Load Diff