From ec0f24cb78724db456dbcaa7dfa83d3e3fd86e71 Mon Sep 17 00:00:00 2001 From: HSZemi Date: Tue, 7 Nov 2023 23:28:47 +0100 Subject: [PATCH] Support categories in Preset editor --- src/actions/index.tsx | 32 ++++++- .../PresetEditor/ActionDropdown.tsx | 1 + .../PresetEditor/PlayerTurnSettings.tsx | 5 +- src/components/PresetEditor/PresetEditor.tsx | 37 +++++++- src/components/PresetEditor/PresetOption.tsx | 24 ++++++ .../PresetEditor/TurnCategoriesInput.tsx | 74 ++++++++++++++++ src/constants/index.tsx | 2 + src/languages/de_DE.json | 12 ++- src/languages/en_GB.json | 13 ++- src/models/Turn.ts | 6 +- src/reducers/presetEditor.tsx | 84 ++++++++++++++++++- 11 files changed, 278 insertions(+), 12 deletions(-) create mode 100644 src/components/PresetEditor/TurnCategoriesInput.tsx diff --git a/src/actions/index.tsx b/src/actions/index.tsx index 9679ad0..60da552 100644 --- a/src/actions/index.tsx +++ b/src/actions/index.tsx @@ -175,6 +175,18 @@ export interface ISetEditorDraftOptions { value: DraftOption[] } +export interface ISetEditorCategoryLimitPick { + type: Actions.SET_EDITOR_CATEGORY_LIMIT_PICK + key: string + value: number | null +} + +export interface ISetEditorCategoryLimitBan { + type: Actions.SET_EDITOR_CATEGORY_LIMIT_BAN + key: string + value: number | null +} + export interface ISetHighlightedAction { type: Actions.SET_HIGHLIGHTED_ACTION, value: number | null; @@ -255,7 +267,9 @@ export type PresetEditorAction = ISetEditorPreset | IDuplicateEditorTurn | ISetEditorTurnOrder | ISetEditorName - | ISetEditorDraftOptions; + | ISetEditorDraftOptions + | ISetEditorCategoryLimitPick + | ISetEditorCategoryLimitBan; export type RecentDraftsAction = ISpectateDrafts | IUnspectateDrafts @@ -490,6 +504,22 @@ export function setEditorDraftOptions(value: DraftOption[]): ISetEditorDraftOpti } } +export function setEditorCategoryLimitPick(key: string, value: number | null): ISetEditorCategoryLimitPick { + return { + key, + value, + type: Actions.SET_EDITOR_CATEGORY_LIMIT_PICK + } +} + +export function setEditorCategoryLimitBan(key: string, value: number | null): ISetEditorCategoryLimitBan { + return { + key, + value, + type: Actions.SET_EDITOR_CATEGORY_LIMIT_BAN + } +} + export function setCountdownValue(value: ICountdownValues): ICountdownEvent { return { type: ServerActions.SET_COUNTDOWN_VALUE, diff --git a/src/components/PresetEditor/ActionDropdown.tsx b/src/components/PresetEditor/ActionDropdown.tsx index 02cde6c..46bc4f8 100644 --- a/src/components/PresetEditor/ActionDropdown.tsx +++ b/src/components/PresetEditor/ActionDropdown.tsx @@ -21,6 +21,7 @@ class ActionDropdown extends React.Component { options.push(); options.push(); options.push(); + options.push(); options.push(); options.push(); options.push(); diff --git a/src/components/PresetEditor/PlayerTurnSettings.tsx b/src/components/PresetEditor/PlayerTurnSettings.tsx index 2dbe157..fc68fb0 100644 --- a/src/components/PresetEditor/PlayerTurnSettings.tsx +++ b/src/components/PresetEditor/PlayerTurnSettings.tsx @@ -7,11 +7,12 @@ import ExclusivityDropdown from "./ExclusivityDropdown"; import ParallelCheckbox from "./ParallelCheckbox"; import AsOpponentCheckbox from "./AsOpponentCheckbox"; import AsPlayerDropdown from "./AsPlayerDropdown"; +import TurnCategoriesInput from "./TurnCategoriesInput"; interface Props { turn: Turn, player: Player, - index: number + index: number, } class PlayerTurnSettings extends React.Component { @@ -25,6 +26,7 @@ class PlayerTurnSettings extends React.Component {
+ ; } return @@ -36,6 +38,7 @@ class PlayerTurnSettings extends React.Component {   + ; } } diff --git a/src/components/PresetEditor/PresetEditor.tsx b/src/components/PresetEditor/PresetEditor.tsx index ef88e9e..70afa26 100644 --- a/src/components/PresetEditor/PresetEditor.tsx +++ b/src/components/PresetEditor/PresetEditor.tsx @@ -6,6 +6,8 @@ import {Dispatch} from "redux"; import * as actions from "../../actions"; import { IDuplicateEditorTurn, + ISetEditorCategoryLimitBan, + ISetEditorCategoryLimitPick, ISetEditorDraftOptions, ISetEditorName, ISetEditorPreset, @@ -42,6 +44,8 @@ interface Props extends WithTranslation, RouteComponentProps { onTurnOrderChange: (turns: Turn[]) => ISetEditorTurnOrder, onPresetNameChange: (value: string) => ISetEditorName, onPresetDraftOptionsChange: (value: DraftOption[]) => ISetEditorDraftOptions + onSetCategoryLimitPick: (key: string, value: number | null) => ISetEditorCategoryLimitPick + onSetCategoryLimitBan: (key: string, value: number | null) => ISetEditorCategoryLimitBan } interface State { @@ -150,6 +154,18 @@ class PresetEditor extends React.Component { const customOptions: boolean = this.state.defaultDraftOptions.length === 0; let optionsSelection = customOptions ? : ; + const categories = [...new Set(this.props.preset.options.map(value => value.category))].sort(); + const categoryInputsPick = categories.map(category =>
  • {category}: this.props.onSetCategoryLimitPick(category, parseInt(event.target.value) || null)} + key={'categoryInputsBan-' + category}/> +
  • ); + const categoryInputsBan = categories.map(category =>
  • {category}: this.props.onSetCategoryLimitBan(category, parseInt(event.target.value) || null)} + key={'categoryInputsBan-' + category}/> +
  • ); + return (
    @@ -291,7 +307,24 @@ class PresetEditor extends React.Component {

    -

    3. Create Draft or Save

    + +

    3. Category Limits

    +

    Here you may, for each category, + define a maximum number of times Draft Options from it can be picked or banned. + Leave the input emtpy to set no limit for a category.

    +
    +
    +

    Pick

    +
      {categoryInputsPick}
    +
    +
    +

    Ban

    +
      {categoryInputsBan}
    +
    +
    +
    + +

    4. Create Draft or Save

    ) { onTurnOrderChange: (turns: Turn[]) => dispatch(actions.setEditorTurnOrder(turns)), onPresetDraftOptionsChange: (value: DraftOption[]) => dispatch(actions.setEditorDraftOptions(value)), onPresetNameChange: (value: string) => dispatch(actions.setEditorName(value)), + onSetCategoryLimitPick: (key: string, value: number | null) => dispatch(actions.setEditorCategoryLimitPick(key, value)), + onSetCategoryLimitBan: (key: string, value: number | null) => dispatch(actions.setEditorCategoryLimitBan(key, value)), } } diff --git a/src/components/PresetEditor/PresetOption.tsx b/src/components/PresetEditor/PresetOption.tsx index 7f13106..3e29059 100644 --- a/src/components/PresetEditor/PresetOption.tsx +++ b/src/components/PresetEditor/PresetOption.tsx @@ -105,6 +105,21 @@ class PresetOption extends React.Component { }}/>

    +
    +
    + +
    +
    + { + this.updateCategory(event.target.value); + }}/> +
    +
    { }); this.props.onPresetDraftOptionsChange(draftOptions); } + private updateCategory(value: string) { + if (this.props.preset === null || this.props.preset === undefined || this.props.preset.options === undefined) { + return; + } + const draftOptions = [...this.props.preset?.options]; + const oldDraftOption = draftOptions[this.props.draftOptionIndex]; + draftOptions[this.props.draftOptionIndex] = new DraftOption(oldDraftOption.id, oldDraftOption.name, oldDraftOption.imageUrls, oldDraftOption.i18nPrefix, value); + this.props.onPresetDraftOptionsChange(draftOptions); + } } export function mapStateToProps(state: ApplicationState) { diff --git a/src/components/PresetEditor/TurnCategoriesInput.tsx b/src/components/PresetEditor/TurnCategoriesInput.tsx new file mode 100644 index 0000000..4dd50e5 --- /dev/null +++ b/src/components/PresetEditor/TurnCategoriesInput.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import {Action as ReducerAction, ISetEditorTurn, setEditorTurn} from "../../actions"; +import Turn from "../../models/Turn"; +import {Dispatch} from "redux"; +import {connect} from "react-redux"; +import Preset from "../../models/Preset"; +import {ApplicationState} from "../../types"; +import {Trans} from "react-i18next"; + +interface Props { + turn: Turn, + preset: Preset | null, + index: number, + onValueChange: (turn: Turn, index: number) => ISetEditorTurn +} + +class TurnCategoriesInput extends React.Component { + private readonly delimiter = '|'; + + render() { + return
    +
    +
    + +
    +
    +
    +

    + { + this.updateAllowedCategories(event.target.value); + }} key={'categoryinput-' + this.props.index}/> +

    +
    +
    + +
    +
    +
    +
    + } + + private updateAllowedCategories(value: string) { + const t = this.props.turn; + const newTurn = new Turn(t.player, t.action, t.exclusivity, t.hidden, t.parallel, t.executingPlayer, value.split(this.delimiter), t.id); + this.props.onValueChange(newTurn, this.props.index); + } + + private setAllCategoriesAsAllowed() { + const categories = [...new Set(this.props.preset?.options.map(value => value.category))].sort(); + const t = this.props.turn; + const newTurn = new Turn(t.player, t.action, t.exclusivity, t.hidden, t.parallel, t.executingPlayer, categories, t.id); + this.props.onValueChange(newTurn, this.props.index); + } +} + +export function mapStateToProps(state: ApplicationState) { + return { + preset: state.presetEditor.editorPreset, + } +} + +export function mapDispatchToProps(dispatch: Dispatch) { + return { + onValueChange: (turn: Turn, index: number) => dispatch(setEditorTurn(turn, index)), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(TurnCategoriesInput); diff --git a/src/constants/index.tsx b/src/constants/index.tsx index 87bc0f6..e9818b4 100644 --- a/src/constants/index.tsx +++ b/src/constants/index.tsx @@ -13,6 +13,8 @@ export enum Actions { SET_EDITOR_TURN_ORDER = 'SET_EDITOR_TURN_ORDER', SET_EDITOR_NAME = 'SET_EDITOR_NAME', SET_EDITOR_DRAFT_OPTIONS = 'SET_EDITOR_DRAFT_OPTIONS', + SET_EDITOR_CATEGORY_LIMIT_PICK = 'SET_EDITOR_CATEGORY_LIMIT_PICK', + SET_EDITOR_CATEGORY_LIMIT_BAN = 'SET_EDITOR_CATEGORY_LIMIT_BAN', SET_DRAFT_EVENTS = 'SET_DRAFT_EVENTS', SET_COLOR_SCHEME = 'SET_COLOR_SCHEME', SET_HIGHLIGHTED_ACTION = 'SET_HIGHLIGHTED_ACTION', diff --git a/src/languages/de_DE.json b/src/languages/de_DE.json index 245a109..5e3638e 100644 --- a/src/languages/de_DE.json +++ b/src/languages/de_DE.json @@ -221,6 +221,9 @@ "VLD_913": "Admin-Züge können nicht versteckt sein", "VLD_914": "Nur Admin-Züge dürfen vorherige versteckte Züge aufdecken", "VLD_915": "Die Beobachterinnenrolle darf keine Züge ausführen", + "VLD_916": "In den erlaubten Kategorien für einen Zug befindet sich eine Kategorie, welche nicht in den Draft-Optionen vorkommt", + "VLD_917": "Eine Draft-Option hat eine Kategorie, die in keinem Zug vorkommt", + "VLD_918": "In den Kategorie-Limits ist ein Limit für eine Kategorie definiert, welche in keinem Zug vorkommt", "VLD_999": "Irgendetwas ist bei der Validierung gehörig schief gelaufen" }, "menu": { @@ -327,6 +330,12 @@ "aoe4Civs": "AoE4-Zivilisationen", "customOptions": "Individuell", "turns": "Züge", + "turnCategories": "Kategorien", + "turnCategoriesAll": "alle", + "categoryLimits": "Kategorie-Limits", + "categoryLimitsExplanation": "Hier kannst du für jede Kategorie die maximale Anzahl von Picks oder Bans über alle Optionen der Kategorie hinweg definieren. Lasse das Eingabefeld leer, um für eine Kategorie kein Limit zu setzen.", + "categoryLimitsPick": "Pick", + "categoryLimitsBan": "Ban", "createSaveDraft": "Draft erstellen oder speichern", "host": "Host", "guest": "Gast", @@ -340,7 +349,8 @@ "unitImageUrl": "URL der Einheitengrafik", "emblemImageUrl": "URL der Emblem-Grafik", "animatedUnitImageUrlLeft": "URL der animierten Einheitengrafik (links)", - "animatedUnitImageUrlRight": "URL der animierten Einheitengrafik (rechts)" + "animatedUnitImageUrlRight": "URL der animierten Einheitengrafik (rechts)", + "categoryName": "Kategorie" } }, "createNewDraft": "Neuen Draft mit diesem Preset erstellen", diff --git a/src/languages/en_GB.json b/src/languages/en_GB.json index db38dff..c33833a 100644 --- a/src/languages/en_GB.json +++ b/src/languages/en_GB.json @@ -221,6 +221,9 @@ "VLD_913": "Admin turns cannot be hidden", "VLD_914": "Only Admin turns can use the reveal action", "VLD_915": "Turns must not involve the specator role", + "VLD_916": "There is a category in the allowed categories for a Turn which is not the category of any Draft Option", + "VLD_917": "There is a Draft Option which has a category that cannot be used in any Turn", + "VLD_918": "The Category Limits contain a category that cannot be used in any Turn", "VLD_999": "Something went very wrong during validation" }, "menu": { @@ -320,12 +323,19 @@ "presetEditor": { "helpAndInstructions": "Help and Instructions", "availableDraftOptions": "Available Draft Options", + "aoe1Civs": "AoE1 civs", "aoe2Civs": "AoE2 civs", "aoe2Maps": "AoE2 maps", "aoe3Civs": "AoE3 civs", "aoe4Civs": "AoE4 civs", "customOptions": "Custom", "turns": "Turns", + "turnCategories": "Categories", + "turnCategoriesAll": "all", + "categoryLimits": "Category Limits", + "categoryLimitsExplanation": "Here you may, for each category, define a maximum number of times Draft Options from it can be picked or banned. Leave the input emtpy to set no limit for a category.", + "categoryLimitsPick": "Pick", + "categoryLimitsBan": "Ban", "createSaveDraft": "Create Draft or Save", "host": "Host", "guest": "Guest", @@ -339,7 +349,8 @@ "unitImageUrl": "Unit image URL", "emblemImageUrl": "Emblem image URL", "animatedUnitImageUrlLeft": "Animated unit image URL (left)", - "animatedUnitImageUrlRight": "Animated unit image URL (right)" + "animatedUnitImageUrlRight": "Animated unit image URL (right)", + "categoryName": "Category" } }, "createNewDraft": "Create new draft from this Preset", diff --git a/src/models/Turn.ts b/src/models/Turn.ts index 3619cbc..08432d6 100644 --- a/src/models/Turn.ts +++ b/src/models/Turn.ts @@ -49,8 +49,8 @@ class Turn { public readonly id: string; public readonly categories: string[]; - constructor(player: Player, action: Action, exclusivity: Exclusivity, hidden: boolean = false, parallel: boolean = false, executingPlayer: Player = player, categories: string[] = ['default']) { - this.id = uuidv4(); + constructor(player: Player, action: Action, exclusivity: Exclusivity, hidden: boolean = false, parallel: boolean = false, executingPlayer: Player = player, categories: string[] = ['default'], id: string = uuidv4()) { + this.id = id; this.player = player; this.action = action; this.exclusivity = exclusivity; @@ -70,7 +70,7 @@ class Turn { Assert.isBoolean(turn.hidden); Assert.isBoolean(turn.parallel); Assert.isOptionalStringArray(turn.categories); - retval.push(new Turn(turn.player, turn.action, turn.exclusivity, turn.hidden, turn.parallel, turn.executingPlayer, turn.categories)); + retval.push(new Turn(turn.player, turn.action, turn.exclusivity, turn.hidden, turn.parallel, turn.executingPlayer, turn.categories, turn.id)); } return retval; } diff --git a/src/reducers/presetEditor.tsx b/src/reducers/presetEditor.tsx index bb7d792..d4ea1c9 100644 --- a/src/reducers/presetEditor.tsx +++ b/src/reducers/presetEditor.tsx @@ -2,6 +2,7 @@ import {IPresetEditorState} from "../types"; import {PresetEditorAction} from "../actions"; import {Actions} from "../constants"; import Preset from "../models/Preset"; +import Turn from "../models/Turn"; export const initialPresetEditorState: IPresetEditorState = { editorPreset: null @@ -44,7 +45,9 @@ export const presetEditorReducer = (state: IPresetEditorState = initialPresetEdi return state; } else { if (editorPreset2.turns.length > action.index) { - editorPreset2.turns.splice(action.index, 0, editorPreset2.turns[action.index]); + const t = editorPreset2.turns[action.index]; + const turnCopy = new Turn(t.player, t.action, t.exclusivity, t.hidden, t.parallel, t.executingPlayer, t.categories); + editorPreset2.turns.splice(action.index, 0, turnCopy); } return { ...state, @@ -63,7 +66,9 @@ export const presetEditorReducer = (state: IPresetEditorState = initialPresetEdi editorPreset: new Preset( state.editorPreset.name, state.editorPreset.options, - action.turns + action.turns, + state.editorPreset.presetId, + state.editorPreset.categoryLimits, ) }; @@ -74,7 +79,13 @@ export const presetEditorReducer = (state: IPresetEditorState = initialPresetEdi } else { return { ...state, - editorPreset: new Preset(action.value, state.editorPreset.options, state.editorPreset.turns) + editorPreset: new Preset( + action.value, + state.editorPreset.options, + state.editorPreset.turns, + state.editorPreset.presetId, + state.editorPreset.categoryLimits, + ) }; } @@ -83,9 +94,74 @@ export const presetEditorReducer = (state: IPresetEditorState = initialPresetEdi if (state.editorPreset === null) { return state; } else { + const categoryLimits = JSON.parse(JSON.stringify(state.editorPreset.categoryLimits));; + const categories = [...new Set(action.value.map(value => value.category))].sort(); + for (let cat in categoryLimits.pick) { + if (!categories.includes(cat)) { + delete categoryLimits.pick[cat]; + } + } + for (let cat in categoryLimits.ban) { + if (!categories.includes(cat)) { + delete categoryLimits.ban[cat]; + } + } + return { + ...state, + editorPreset: new Preset( + state.editorPreset.name, + action.value, + state.editorPreset.turns, + state.editorPreset.presetId, + categoryLimits, + ) + }; + } + case Actions.SET_EDITOR_CATEGORY_LIMIT_PICK: + console.log(Actions.SET_EDITOR_CATEGORY_LIMIT_PICK, action.key, action.value); + if (state.editorPreset === null) { + return state; + } else { + const categoryLimits = JSON.parse(JSON.stringify(state.editorPreset.categoryLimits)); + if (action.value === null) { + delete categoryLimits.pick[action.key]; + } else { + categoryLimits.pick[action.key] = action.value; + } + return { + ...state, + editorPreset: new Preset( + state.editorPreset.name, + state.editorPreset.options, + state.editorPreset.turns, + state.editorPreset.presetId, + categoryLimits, + ) + }; + } + case Actions.SET_EDITOR_CATEGORY_LIMIT_BAN: + console.log(Actions.SET_EDITOR_CATEGORY_LIMIT_BAN, action.key, action.value); + if (state.editorPreset === null) { + return state; + } else { + const categoryLimits = { + pick: state.editorPreset.categoryLimits.pick, + ban: state.editorPreset.categoryLimits.ban + }; + if (action.value === null) { + delete categoryLimits.ban[action.key]; + } else { + categoryLimits.ban[action.key] = action.value; + } return { ...state, - editorPreset: new Preset(state.editorPreset.name, action.value, state.editorPreset.turns) + editorPreset: new Preset( + state.editorPreset.name, + state.editorPreset.options, + state.editorPreset.turns, + state.editorPreset.presetId, + categoryLimits, + ) }; }