Compare commits
19 Commits
231018ce39
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c3fc993f5a | |||
| 64f1ea0be6 | |||
| 9e7b456400 | |||
| 7cc9fd7722 | |||
| c036d7d5ca | |||
| bd271021f1 | |||
| 63b1538a37 | |||
| b0b6e7084e | |||
| 35475e36db | |||
| a743eca07f | |||
| e450020cda | |||
| d095488c74 | |||
| 4273b5c8ca | |||
| cfbb358115 | |||
| 39df588c7a | |||
| ba61a5258c | |||
| 3412e78237 | |||
| a913d2d5cb | |||
| 49fb1d90a0 |
30
.eslintrc.yml
Normal file
30
.eslintrc.yml
Normal 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
|
||||
@@ -9,6 +9,7 @@
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"autoprefixer": "^9",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"postcss": "^7",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
@@ -39,5 +40,10 @@
|
||||
"last 1 firefox 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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,63 +6,63 @@ import Side from "./types/Side"
|
||||
* @param item what item
|
||||
* @param side CT or T
|
||||
*/
|
||||
function ItemToDisplay(item:Item, side: Side ):string {
|
||||
switch (item) {
|
||||
case Item.DEFAULT_PISTOL:
|
||||
return side === Side.CT ? "USP-S / P2000":"Glock-18"
|
||||
case Item.DUAL_BERETTAS:
|
||||
return "Dual Berettas"
|
||||
case Item.P250:
|
||||
return "P250"
|
||||
case Item.TEC_9_FIVE_SEVEN:
|
||||
return side === Side.CT ? "Five-Seven / CZ75-Auto":"Tec-9 / CZ75-Auto"
|
||||
case Item.DESERT_EAGLE:
|
||||
return "Desert Eagle / R8 Revolver"
|
||||
case Item.NOVA:
|
||||
return "Nova"
|
||||
case Item.XM1014:
|
||||
return "XM1014"
|
||||
case Item.MAG_7:
|
||||
return side === Side.CT ? "MAG-7":"Sawed-Off"
|
||||
case Item.M249:
|
||||
return "M249"
|
||||
case Item.NEGEV:
|
||||
return "Negev"
|
||||
case Item.MAC_10_MP9:
|
||||
return side === Side.CT ? "MP9":"MAC-10"
|
||||
case Item.MP7:
|
||||
return "MP7 / MP5-SD"
|
||||
case Item.UMP_45:
|
||||
return "UMP-45"
|
||||
case Item.P90:
|
||||
return "P90"
|
||||
case Item.PP_BIZON:
|
||||
return "PP-Bizon"
|
||||
case Item.FAMAS_GALIL:
|
||||
return side === Side.CT ? "FAMAS":"Galil AR"
|
||||
case Item.AK_47_M4:
|
||||
return side === Side.CT ? "M4A4 / M4A1-S":"AK-47"
|
||||
case Item.SSG_08:
|
||||
return "SSG 08"
|
||||
case Item.SG_554_AUG:
|
||||
return side === Side.CT ? "AUG":"SG 553"
|
||||
case Item.AWP:
|
||||
return "AWP"
|
||||
case Item.G3_SCAR:
|
||||
return side === Side.CT ? "SCAR-20":"G3SG1"
|
||||
case Item.MOLOTOV:
|
||||
return side === Side.CT ? "Incendiary Grenade":"Molotov"
|
||||
case Item.DECOY:
|
||||
return "Decoy"
|
||||
case Item.FLASHBANG:
|
||||
return "Flashbang"
|
||||
case Item.HE:
|
||||
return "HE Grenade"
|
||||
case Item.SMOKE:
|
||||
return "Smoke"
|
||||
default:
|
||||
throw new Error("Item not translatable")
|
||||
}
|
||||
function ItemToDisplay(item:Item, side: Side ):string {
|
||||
switch (item) {
|
||||
case Item.DEFAULT_PISTOL:
|
||||
return side === Side.CT ? "USP-S / P2000":"Glock-18"
|
||||
case Item.DUAL_BERETTAS:
|
||||
return "Dual Berettas"
|
||||
case Item.P250:
|
||||
return "P250"
|
||||
case Item.TEC_9_FIVE_SEVEN:
|
||||
return side === Side.CT ? "Five-Seven / CZ75-Auto":"Tec-9 / CZ75-Auto"
|
||||
case Item.DESERT_EAGLE:
|
||||
return "Desert Eagle / R8 Revolver"
|
||||
case Item.NOVA:
|
||||
return "Nova"
|
||||
case Item.XM1014:
|
||||
return "XM1014"
|
||||
case Item.MAG_7:
|
||||
return side === Side.CT ? "MAG-7":"Sawed-Off"
|
||||
case Item.M249:
|
||||
return "M249"
|
||||
case Item.NEGEV:
|
||||
return "Negev"
|
||||
case Item.MAC_10_MP9:
|
||||
return side === Side.CT ? "MP9":"MAC-10"
|
||||
case Item.MP7:
|
||||
return "MP7 / MP5-SD"
|
||||
case Item.UMP_45:
|
||||
return "UMP-45"
|
||||
case Item.P90:
|
||||
return "P90"
|
||||
case Item.PP_BIZON:
|
||||
return "PP-Bizon"
|
||||
case Item.FAMAS_GALIL:
|
||||
return side === Side.CT ? "FAMAS":"Galil AR"
|
||||
case Item.AK_47_M4:
|
||||
return side === Side.CT ? "M4A4 / M4A1-S":"AK-47"
|
||||
case Item.SSG_08:
|
||||
return "SSG 08"
|
||||
case Item.SG_554_AUG:
|
||||
return side === Side.CT ? "AUG":"SG 553"
|
||||
case Item.AWP:
|
||||
return "AWP"
|
||||
case Item.G3_SCAR:
|
||||
return side === Side.CT ? "SCAR-20":"G3SG1"
|
||||
case Item.MOLOTOV:
|
||||
return side === Side.CT ? "Incendiary Grenade":"Molotov"
|
||||
case Item.DECOY:
|
||||
return "Decoy"
|
||||
case Item.FLASHBANG:
|
||||
return "Flashbang"
|
||||
case Item.HE:
|
||||
return "HE Grenade"
|
||||
case Item.SMOKE:
|
||||
return "Smoke"
|
||||
default:
|
||||
throw new Error("Item not translatable")
|
||||
}
|
||||
}
|
||||
|
||||
export default ItemToDisplay
|
||||
|
||||
@@ -9,26 +9,26 @@ interface Props {
|
||||
}
|
||||
|
||||
const AddItem: React.FC<Props> = ({side,onChange}) => {
|
||||
const [selected,setSelected] = useState<Item>(Item.DEFAULT_PISTOL)
|
||||
const [selected,setSelected] = useState<Item>(Item.DEFAULT_PISTOL)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<select
|
||||
className="bg-gray-600 border-2 border-gray-700"
|
||||
onChange={(e)=>setSelected(Item[e.target.value as keyof typeof Item])}
|
||||
>
|
||||
{
|
||||
Object.keys(Item).map(e=><option key={e} value={e}>{ItemToDisplay(Item[e as keyof typeof Item],side)}</option>)
|
||||
}
|
||||
</select>
|
||||
<span
|
||||
onClick={()=>onChange?.(selected)}
|
||||
className="cursor-pointer float-right"
|
||||
>
|
||||
return (
|
||||
<div>
|
||||
<select
|
||||
className="bg-gray-600 border-2 border-gray-700"
|
||||
onChange={(e)=>setSelected(Item[e.target.value as keyof typeof Item])}
|
||||
>
|
||||
{
|
||||
Object.keys(Item).map(e=><option key={e} value={e}>{ItemToDisplay(Item[e as keyof typeof Item],side)}</option>)
|
||||
}
|
||||
</select>
|
||||
<span
|
||||
onClick={()=>onChange?.(selected)}
|
||||
className="cursor-pointer float-right"
|
||||
>
|
||||
Add
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddItem
|
||||
|
||||
@@ -1,35 +1,52 @@
|
||||
import React, { useState } from "react"
|
||||
import React from "react"
|
||||
import RetakesConfig from "../types/RetakesConfig"
|
||||
import Side from "../types/Side"
|
||||
import CardComp from "./Card"
|
||||
import DeckComp from "./Deck"
|
||||
|
||||
const AllDecks: React.FC = () => {
|
||||
const [retakesConfig,setRetakesConfig] = useState(new RetakesConfig())
|
||||
interface Props {
|
||||
retakesConfig: RetakesConfig
|
||||
onChange?: (newConfig: RetakesConfig)=>void
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DeckComp title="CT Pistol" side={Side.CT} deck={retakesConfig.ctPistol}
|
||||
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{ctPistol:newDeck}}))} />
|
||||
<DeckComp title="T Pistol" side={Side.T} deck={retakesConfig.tPistol}
|
||||
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{tPistol:newDeck}}))} />
|
||||
const AllDecks: React.FC<Props> = ({retakesConfig,onChange}) => {
|
||||
return (
|
||||
<div>
|
||||
<DeckComp title="CT Pistol" side={Side.CT} deck={retakesConfig.ctPistol}
|
||||
onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{ctPistol:newDeck}}))} />
|
||||
<DeckComp title="T Pistol" side={Side.T} deck={retakesConfig.tPistol}
|
||||
onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{tPistol:newDeck}}))} />
|
||||
|
||||
<DeckComp title="CT Upgraded Pistol" side={Side.CT} deck={retakesConfig.ctUpgradedPistol}
|
||||
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{ctUpgradedPistol:newDeck}}))} />
|
||||
<DeckComp title="T Upgraded Pistol" side={Side.T} deck={retakesConfig.tUpgradedPistol}
|
||||
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{tUpgradedPistol:newDeck}}))} />
|
||||
<DeckComp title="CT Upgraded Pistol" side={Side.CT} deck={retakesConfig.ctUpgradedPistol}
|
||||
onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{ctUpgradedPistol:newDeck}}))} />
|
||||
<DeckComp title="T Upgraded Pistol" side={Side.T} deck={retakesConfig.tUpgradedPistol}
|
||||
onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{tUpgradedPistol:newDeck}}))} />
|
||||
|
||||
<DeckComp title="CT Light" side={Side.CT} deck={retakesConfig.ctLight}
|
||||
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{ctLight:newDeck}}))} />
|
||||
<DeckComp title="T Light" side={Side.T} deck={retakesConfig.tLight}
|
||||
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{tLight:newDeck}}))} />
|
||||
<DeckComp title="CT Light" side={Side.CT} deck={retakesConfig.ctLight}
|
||||
onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{ctLight:newDeck}}))} />
|
||||
<DeckComp title="T Light" side={Side.T} deck={retakesConfig.tLight}
|
||||
onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{tLight:newDeck}}))} />
|
||||
|
||||
<DeckComp title="CT Full" side={Side.CT} deck={retakesConfig.ctFull}
|
||||
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{ctFull:newDeck}}))} />
|
||||
<DeckComp title="T Full" side={Side.T} deck={retakesConfig.tFull}
|
||||
onChange={(newDeck)=> setRetakesConfig(new RetakesConfig({...retakesConfig,...{tFull:newDeck}}))} />
|
||||
<DeckComp title="CT Full" side={Side.CT} deck={retakesConfig.ctFull}
|
||||
onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{ctFull:newDeck}}))} />
|
||||
<DeckComp title="T Full" side={Side.T} deck={retakesConfig.tFull}
|
||||
onChange={(newDeck)=> onChange?.(new RetakesConfig({...retakesConfig,...{tFull:newDeck}}))} />
|
||||
|
||||
</div>
|
||||
)
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
export default AllDecks
|
||||
@@ -1,22 +1,63 @@
|
||||
import React from 'react';
|
||||
import AllDecks from './AllDecks';
|
||||
import React, { useEffect, useState } from "react"
|
||||
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 [CTDeck,setCTDeck] = useState<Deck>(new Deck(
|
||||
// 2,new CardGroup(
|
||||
// 2,new Card("AWE fake",false,false,Item.AWP,Item.DECOY),
|
||||
// new Card("M4 Flash",true,true,Item.AK_47_M4,Item.FLASHBANG)
|
||||
// ),
|
||||
// new CardGroup(
|
||||
// 3,new Card("One deag",true,false,Item.DESERT_EAGLE)
|
||||
// )
|
||||
// ))
|
||||
const App: React.FC = () => {
|
||||
const [retakesConfig,setRetakesConfig] = useState(new RetakesConfig())
|
||||
const [showExport,setShowExport] = useState(false)
|
||||
const [exportText,setExportText] = useState("")
|
||||
|
||||
return (
|
||||
<div className="text-white">
|
||||
<AllDecks />
|
||||
</div>
|
||||
);
|
||||
useEffect(()=>{
|
||||
// Load saved config
|
||||
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 (
|
||||
<div className="text-white">
|
||||
<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
|
||||
className="bg-gray-700 button flex justify-center"
|
||||
onClick={()=>copy(exportText)}
|
||||
>Copy to clipboard</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App
|
||||
|
||||
@@ -14,35 +14,35 @@ interface Props {
|
||||
}
|
||||
|
||||
const CardComp: React.FC<Props> = ({card,side,onChange,onRemove}) => {
|
||||
return (
|
||||
<div className="bg-gray-600 m-1 p-1 w-72 min-h-12">
|
||||
<span className="float-right cursor-pointer" onClick={()=>{onRemove?.()}}>X</span>
|
||||
<span className="font-bold"><TextEdit
|
||||
text={card.title}
|
||||
onChange={(newTitle)=>onChange?.(new Card(newTitle,card.armor,card.helmet,...card.items))}
|
||||
/></span>
|
||||
<div>
|
||||
<span>
|
||||
return (
|
||||
<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="font-bold"><TextEdit
|
||||
text={card.title}
|
||||
onChange={(newTitle)=>onChange?.(new Card(newTitle,card.armor,card.helmet,...card.items))}
|
||||
/></span>
|
||||
<div>
|
||||
<span>
|
||||
Helmet: <SwitchButton active={card.helmet} onChange={(to)=>onChange?.(new Card(card.title,card.armor,to,...card.items))} />
|
||||
</span>
|
||||
<span className="float-right">
|
||||
</span>
|
||||
<span className="float-right">
|
||||
Armor: <SwitchButton active={card.armor} onChange={(to)=>onChange?.(new Card(card.title,to,card.helmet,...card.items))} />
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-2 border-gray-700 p-0.5">
|
||||
{card.items.map((item,i)=>
|
||||
<div
|
||||
key={item + i} // FIXME: this is just a "temporary" solution. Implement propper key
|
||||
>
|
||||
{ItemToDisplay(item,side)} <span onClick={()=>{
|
||||
onChange?.(new Card(card.title,card.armor,card.helmet,...card.items.filter((_,fi)=>i!==fi)))
|
||||
}} className="float-right cursor-pointer" >X</span>
|
||||
</div>
|
||||
)}
|
||||
<AddItem side={side} onChange={newItem => onChange?.(new Card(card.title,card.armor,card.helmet,...card.items,newItem))} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-2 border-gray-700 p-0.5">
|
||||
{card.items.map((item,i)=>
|
||||
<div
|
||||
key={item + i} // FIXME: this is just a "temporary" solution. Implement propper key
|
||||
>
|
||||
{ItemToDisplay(item,side)} <span onClick={()=>{
|
||||
onChange?.(new Card(card.title,card.armor,card.helmet,...card.items.filter((_,fi)=>i!==fi)))
|
||||
}} className="float-right cursor-pointer" >X</span>
|
||||
</div>
|
||||
)}
|
||||
<AddItem side={side} onChange={newItem => onChange?.(new Card(card.title,card.armor,card.helmet,...card.items,newItem))} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardComp
|
||||
|
||||
@@ -38,7 +38,7 @@ const CardGroupComp: React.FC<Props> = ({cardGroup,side,onChange,onRemove}) => {
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="bg-gray-600 m-1 p-1 cursor-pointer my-auto"
|
||||
className="bg-gray-600 my-auto button"
|
||||
onClick={()=>onChange?.(
|
||||
new CardGroup(
|
||||
cardGroup.numInDeck,
|
||||
|
||||
@@ -14,50 +14,50 @@ interface Props {
|
||||
}
|
||||
|
||||
const DeckComp: React.FC<Props> = ({deck,side,title,onChange}) => {
|
||||
return (
|
||||
<div className="bg-gray-800 m-1 p-1">
|
||||
<span className="font-bold">{title}</span>
|
||||
{ side === Side.CT && <span>
|
||||
D: <NumEdit
|
||||
value={deck.numDefusers}
|
||||
onChange={newNum => onChange?.(new Deck(Math.max(newNum,0),...deck.cardGroups))}
|
||||
/> </span> }
|
||||
<div className="flex">
|
||||
{deck.cardGroups.map((group,index) =>
|
||||
<CardGroupComp
|
||||
key={group.toString() + index} // FIXME: this is just a "temporary" solution. Implement propper key
|
||||
cardGroup={group}
|
||||
side={side}
|
||||
onChange={(newGroup)=>{
|
||||
onChange?.(new Deck(
|
||||
deck.numDefusers,
|
||||
...deck.cardGroups.map((e,i)=>i===index?newGroup:e)
|
||||
))
|
||||
}}
|
||||
onRemove={()=>{
|
||||
onChange?.(new Deck(
|
||||
deck.numDefusers,
|
||||
...deck.cardGroups.filter((e,i)=>i!==index)
|
||||
))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="bg-gray-700 m-1 p-1 cursor-pointer my-auto"
|
||||
onClick={()=>onChange?.(
|
||||
new Deck(
|
||||
deck.numDefusers,
|
||||
...deck.cardGroups,
|
||||
new CardGroup(1,new Card("New card",false,false))
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
return (
|
||||
<div className="bg-gray-800 m-1 p-1 overflow-x-scroll">
|
||||
<span className="font-bold">{title}</span>
|
||||
{ side === Side.CT && <span className="px-2">
|
||||
Defuser: <NumEdit
|
||||
value={deck.numDefusers}
|
||||
onChange={newNum => onChange?.(new Deck(Math.max(newNum,0),...deck.cardGroups))}
|
||||
/> </span> }
|
||||
<div className="flex">
|
||||
{deck.cardGroups.map((group,index) =>
|
||||
<CardGroupComp
|
||||
key={group.toString() + index} // FIXME: this is just a "temporary" solution. Implement propper key
|
||||
cardGroup={group}
|
||||
side={side}
|
||||
onChange={(newGroup)=>{
|
||||
onChange?.(new Deck(
|
||||
deck.numDefusers,
|
||||
...deck.cardGroups.map((e,i)=>i===index?newGroup:e)
|
||||
))
|
||||
}}
|
||||
onRemove={()=>{
|
||||
onChange?.(new Deck(
|
||||
deck.numDefusers,
|
||||
...deck.cardGroups.filter((e,i)=>i!==index)
|
||||
))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="bg-gray-700 button my-auto"
|
||||
onClick={()=>onChange?.(
|
||||
new Deck(
|
||||
deck.numDefusers,
|
||||
...deck.cardGroups,
|
||||
new CardGroup(1,new Card("New card",false,false))
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
Add Group
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeckComp
|
||||
|
||||
30
src/components/MenuBar.tsx
Normal file
30
src/components/MenuBar.tsx
Normal 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
26
src/components/Modal.tsx
Normal 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
|
||||
@@ -6,19 +6,19 @@ interface Props {
|
||||
}
|
||||
|
||||
const NumEdit: React.FC<Props> = ({value,onChange}) => {
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className="cursor-pointer select-none"
|
||||
onClick={()=>onChange?.(value - 1)}
|
||||
>-</span>
|
||||
{value}
|
||||
<span
|
||||
className="cursor-pointer select-none"
|
||||
onClick={()=>onChange?.(value + 1)}
|
||||
>+</span>
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className="cursor-pointer select-none"
|
||||
onClick={()=>onChange?.(value - 1)}
|
||||
>-</span>
|
||||
{value}
|
||||
<span
|
||||
className="cursor-pointer select-none"
|
||||
onClick={()=>onChange?.(value + 1)}
|
||||
>+</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default NumEdit
|
||||
|
||||
@@ -6,9 +6,9 @@ interface Props {
|
||||
}
|
||||
|
||||
const SwitchButton: React.FC<Props> = ({active,onChange}) => {
|
||||
return (
|
||||
<button onClick={()=>onChange(!active)} > {active?"YES":"NO"} </button>
|
||||
)
|
||||
return (
|
||||
<button onClick={()=>onChange(!active)} > {active?"YES":"NO"} </button>
|
||||
)
|
||||
}
|
||||
|
||||
export default SwitchButton
|
||||
|
||||
@@ -6,41 +6,43 @@ interface Props {
|
||||
}
|
||||
|
||||
const TextEdit: React.FC<Props> = ({text,onChange}) => {
|
||||
const [edit,setEdit] = useState<boolean>(false)
|
||||
const [inputValue,setinputValue] = useState(text)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const [edit,setEdit] = useState<boolean>(false)
|
||||
const [inputValue,setinputValue] = useState(text)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
useEffect(()=>{
|
||||
if (edit){
|
||||
inputRef.current?.select()
|
||||
}else{
|
||||
if (text !== inputValue){
|
||||
onChange?.(inputValue)
|
||||
}
|
||||
}
|
||||
if (text !== inputValue){
|
||||
onChange?.(inputValue)
|
||||
}
|
||||
}
|
||||
},[edit])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{!edit && <span className="cursor-pointer" onClick={()=>setEdit(true)} >{inputValue}</span>}
|
||||
{edit &&
|
||||
<form className="inline" onSubmit={(e)=>{
|
||||
e.preventDefault()
|
||||
setEdit(false)
|
||||
}} >
|
||||
<input
|
||||
type="text"
|
||||
className="bg-transparent outline-none font-bold"
|
||||
value={inputValue}
|
||||
onChange={(e)=>setinputValue(e.target.value)}
|
||||
onBlur={()=>setEdit(false)}
|
||||
ref={inputRef}
|
||||
></input>
|
||||
</form>
|
||||
}
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{!edit && <span className="cursor-pointer" onClick={()=>setEdit(true)} >{inputValue}</span>}
|
||||
{edit &&
|
||||
<form className="inline"
|
||||
onSubmit={(e)=>{
|
||||
e.preventDefault()
|
||||
setEdit(false)
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
className="bg-transparent outline-none font-bold"
|
||||
value={inputValue}
|
||||
onChange={(e)=>setinputValue(e.target.value)}
|
||||
onBlur={()=>setEdit(false)}
|
||||
ref={inputRef}
|
||||
></input>
|
||||
</form>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextEdit
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.min-h-12 {
|
||||
min-height: 12rem;
|
||||
.min-h-card {
|
||||
min-height: 10rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply py-1 px-2 mx-2 hover:bg-gray-900 cursor-pointer
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './components/App';
|
||||
import './index.css';
|
||||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import App from "./components/App"
|
||||
import "./index.css"
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById("root")
|
||||
)
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
import Item from "./Item"
|
||||
|
||||
class Card {
|
||||
readonly title: string
|
||||
readonly armor: boolean
|
||||
readonly helmet: boolean
|
||||
readonly items: Item[]
|
||||
readonly title: string
|
||||
readonly armor: boolean
|
||||
readonly helmet: boolean
|
||||
readonly items: Item[]
|
||||
|
||||
constructor(title: string,armor: boolean,helmet: boolean,...items:Item[]){
|
||||
this.title = title
|
||||
this.armor = armor
|
||||
this.helmet = helmet
|
||||
this.items = items
|
||||
}
|
||||
constructor(title: string,armor: boolean,helmet: boolean,...items:Item[]){
|
||||
this.title = title
|
||||
this.armor = armor
|
||||
this.helmet = helmet
|
||||
this.items = items
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `${this.title},${bToS(this.armor)},${bToS(this.helmet)},${this.items.join(",")}`
|
||||
}
|
||||
public toCvar(): string {
|
||||
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[]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a boolean to 1 for true and 0 for false
|
||||
*/
|
||||
function bToS(bool: boolean): string{
|
||||
return bool?"1":"0"
|
||||
function bToS(bool: boolean): string{
|
||||
return bool?"1":"0"
|
||||
}
|
||||
|
||||
export default Card
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import Card from "./Card"
|
||||
|
||||
class CardGroup {
|
||||
readonly numInDeck: number
|
||||
readonly cards: Card[]
|
||||
readonly numInDeck: number
|
||||
readonly cards: Card[]
|
||||
|
||||
constructor(numInDeck: number, ...cards: Card[]) {
|
||||
this.numInDeck = numInDeck
|
||||
this.cards = cards
|
||||
}
|
||||
constructor(numInDeck: number, ...cards: Card[]) {
|
||||
this.numInDeck = numInDeck
|
||||
this.cards = cards
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `${this.numInDeck};${this.cards.map(e => e.toString()).join(";")}`
|
||||
}
|
||||
public toCvar(): string {
|
||||
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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CardGroup
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import CardGroup from "./CardGroup"
|
||||
|
||||
class Deck {
|
||||
readonly numDefusers: number
|
||||
readonly cardGroups: CardGroup[]
|
||||
readonly numDefusers: number
|
||||
readonly cardGroups: CardGroup[]
|
||||
|
||||
constructor(numDefusers: number,...cardGroups: CardGroup[]){
|
||||
this.numDefusers = numDefusers
|
||||
this.cardGroups = cardGroups
|
||||
}
|
||||
constructor(numDefusers: number,...cardGroups: CardGroup[]){
|
||||
this.numDefusers = numDefusers
|
||||
this.cardGroups = cardGroups
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `${this.numDefusers}|${this.cardGroups.map(e => e.toString()).join("|")}`
|
||||
}
|
||||
public toCvar(): string {
|
||||
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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Deck
|
||||
|
||||
@@ -2,29 +2,30 @@ import Card from "./Card"
|
||||
import Deck from "./Deck"
|
||||
|
||||
class RetakesConfig {
|
||||
readonly ctPistol: Deck
|
||||
readonly tPistol: Deck
|
||||
readonly ctPistol: Deck = new Deck(0)
|
||||
readonly tPistol: Deck = new Deck(0)
|
||||
|
||||
readonly ctUpgradedPistol: Deck
|
||||
readonly tUpgradedPistol: Deck
|
||||
readonly ctUpgradedPistol: Deck = new Deck(0)
|
||||
readonly tUpgradedPistol: Deck = new Deck(0)
|
||||
|
||||
readonly ctLight: Deck
|
||||
readonly tLight: Deck
|
||||
readonly ctLight: Deck = new Deck(0)
|
||||
readonly tLight: Deck = new Deck(0)
|
||||
|
||||
readonly ctFull: Deck
|
||||
readonly tFull: Deck
|
||||
readonly ctFull: Deck = new Deck(0)
|
||||
readonly tFull: Deck = new Deck(0)
|
||||
|
||||
readonly ctEnemy: Card
|
||||
readonly tEnemy: Card
|
||||
readonly ctEnemy: Card = new Card("CT Enemy card",false,false)
|
||||
readonly tEnemy: Card = new Card("T Enemy card",false,false)
|
||||
|
||||
readonly ctBonus: Card
|
||||
readonly tBonus: Card
|
||||
readonly ctBonus: Card = new Card("CT Bonus card",false,false)
|
||||
readonly tBonus: Card = new Card("T Bonus card",false,false)
|
||||
|
||||
readonly ctBonusAvailability: number[]
|
||||
readonly tBonusAvailability: number[]
|
||||
// If not set to [1] or a valid value the game will crash
|
||||
readonly ctBonusAvailability: number[] = [1]
|
||||
readonly tBonusAvailability: number[] = [1]
|
||||
|
||||
// TODO: there must be some smarter way to do this
|
||||
constructor(args? :{
|
||||
// TODO: there must be some smarter way to do this
|
||||
constructor(args? :{
|
||||
ctPistol?: Deck,
|
||||
tPistol?: Deck,
|
||||
ctUpgradedPistol?: Deck,
|
||||
@@ -40,31 +41,59 @@ class RetakesConfig {
|
||||
ctBonusAvailability?: number[],
|
||||
tBonusAvailability?: number[],
|
||||
}){
|
||||
this.ctPistol = args?.ctPistol ?? new Deck(0)
|
||||
this.tPistol = args?.tPistol ?? new Deck(0)
|
||||
if (args){
|
||||
args.ctPistol && (this.ctPistol = args.ctPistol)
|
||||
args.tPistol && (this.tPistol = args.tPistol)
|
||||
args.ctUpgradedPistol && (this.ctUpgradedPistol = args.ctUpgradedPistol)
|
||||
args.tUpgradedPistol && (this.tUpgradedPistol = args.tUpgradedPistol)
|
||||
args.ctLight && (this.ctLight = args.ctLight)
|
||||
args.tLight && (this.tLight = args.tLight)
|
||||
args.ctFull && (this.ctFull = args.ctFull)
|
||||
args.tFull && (this.tFull = args.tFull)
|
||||
args.ctEnemy && (this.ctEnemy = args.ctEnemy)
|
||||
args.tEnemy && (this.tEnemy = args.tEnemy)
|
||||
args.ctBonus && (this.ctBonus = args.ctBonus)
|
||||
args.tBonus && (this.tBonus = args.tBonus)
|
||||
args.ctBonusAvailability && (this.ctBonusAvailability = args.ctBonusAvailability)
|
||||
args.tBonusAvailability && (this.tBonusAvailability = args.tBonusAvailability)
|
||||
}
|
||||
}
|
||||
|
||||
this.ctUpgradedPistol = args?.ctUpgradedPistol ?? new Deck(0)
|
||||
this.tUpgradedPistol = args?.tUpgradedPistol ?? new Deck(0)
|
||||
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(",")}";
|
||||
`
|
||||
}
|
||||
|
||||
this.ctLight = args?.ctLight ?? new Deck(0)
|
||||
this.tLight = args?.tLight ?? new Deck(0)
|
||||
|
||||
this.ctFull = args?.ctFull ?? new Deck(0)
|
||||
this.tFull = args?.tFull ?? new Deck(0)
|
||||
|
||||
this.ctEnemy = args?.ctEnemy ?? new Card("CT Enemy card",false,false)
|
||||
this.tEnemy = args?.tEnemy ?? new Card("T Enemy Card",false,false)
|
||||
|
||||
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(){
|
||||
|
||||
}
|
||||
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[],
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user