From 6ffe47ef1a6b233e6833f0f1adb9f76bc0d3bd46 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 18 Feb 2024 12:05:40 -0500 Subject: [PATCH 1/2] add points remaining and copy to talents --- ui/core/components/copy_button.tsx | 55 ++++++++++++ .../{exporters.ts => exporters.tsx} | 52 +++++------- .../individual_sim_ui/talents_tab.ts | 2 +- .../{glyphs_picker.ts => glyphs_picker.tsx} | 10 ++- ui/core/talents/hunter_pet.ts | 10 +-- ui/core/talents/talents_picker.tsx | 83 +++++++++++++------ ui/hunter/inputs.ts | 2 +- ui/scss/core/components/_exporters.scss | 6 +- ui/scss/core/talents/_talents_picker.scss | 32 +++++-- ui/scss/shared/_mixins.scss | 17 ++++ 10 files changed, 195 insertions(+), 74 deletions(-) create mode 100644 ui/core/components/copy_button.tsx rename ui/core/components/{exporters.ts => exporters.tsx} (93%) rename ui/core/talents/{glyphs_picker.ts => glyphs_picker.tsx} (96%) diff --git a/ui/core/components/copy_button.tsx b/ui/core/components/copy_button.tsx new file mode 100644 index 0000000000..ba69e73c32 --- /dev/null +++ b/ui/core/components/copy_button.tsx @@ -0,0 +1,55 @@ +import { Tooltip } from 'bootstrap'; +// eslint-disable-next-line unused-imports/no-unused-imports +import { element, ref } from 'tsx-vanilla'; + +import { Component } from "./component"; + +export interface CopyButtonConfig { + getContent: () => string, + extraCssClasses?: string[], + text?: string, + tooltip?: string, +} + +export class CopyButton extends Component { + private readonly config: CopyButtonConfig; + + constructor(parent: HTMLElement, config: CopyButtonConfig) { + const btnRef = ref(); + const buttonElem = ( + + ) + + super(parent, 'copy-button', buttonElem as HTMLElement); + this.config = config; + + const button = btnRef.value! + let clicked = false + button.addEventListener('click', _event => { + if (clicked) return + + const data = this.config.getContent() + if (navigator.clipboard == undefined) { + alert(data); + } else { + clicked = true + navigator.clipboard.writeText(data); + const originalContent = button.innerHTML; + button.innerHTML = 'Copied'; + setTimeout(() => { + button.innerHTML = originalContent; + clicked = false + }, 1500); + } + }); + + if (config.tooltip) { + Tooltip.getOrCreateInstance(button, {title: config.tooltip}); + } + } +}; diff --git a/ui/core/components/exporters.ts b/ui/core/components/exporters.tsx similarity index 93% rename from ui/core/components/exporters.ts rename to ui/core/components/exporters.tsx index dacf15d7dc..ff3c1c2693 100644 --- a/ui/core/components/exporters.ts +++ b/ui/core/components/exporters.tsx @@ -1,3 +1,6 @@ +// eslint-disable-next-line unused-imports/no-unused-imports +import { element, ref } from 'tsx-vanilla'; + import { IndividualSimUI } from '../individual_sim_ui'; import { SimUI } from '../sim_ui'; import { @@ -15,7 +18,9 @@ import { IndividualLinkImporter, IndividualWowheadGearPlannerImporter } from './ import { RaidSimRequest } from '../proto/api'; import { SimSettingCategories } from '../sim'; import { EventID, TypedEvent } from '../typed_event'; + import { BooleanPicker } from './boolean_picker'; +import { CopyButton } from './copy_button'; import * as Mechanics from '../constants/mechanics'; @@ -37,41 +42,26 @@ export abstract class Exporter extends BaseModal { this.body.innerHTML = ` `; - this.footer!.innerHTML = ` - - ${options.allowDownload ? ` - - ` : '' - } - `; - this.textElem = this.rootElem.getElementsByClassName('exporter-textarea')[0] as HTMLElement; - const clipboardButton = this.rootElem.getElementsByClassName('clipboard-button')[0] as HTMLElement; - clipboardButton.addEventListener('click', event => { - const data = this.textElem.textContent!; - if (navigator.clipboard == undefined) { - alert(data); - } else { - navigator.clipboard.writeText(data); - const originalContent = clipboardButton.innerHTML; - clipboardButton.style.width = `${clipboardButton.getBoundingClientRect().width.toFixed(3)}px`; - clipboardButton.innerHTML = ` Copied`; - setTimeout(() => { - clipboardButton.innerHTML = originalContent; - }, 1500); - } - }); + new CopyButton(this.footer!, { + extraCssClasses: ['btn-primary', 'me-2'], + getContent: () => this.textElem.innerHTML, + text: 'Copy', + tooltip: 'Copy to clipboard', + }) if (options.allowDownload) { - const downloadButton = this.rootElem.getElementsByClassName('download-button')[0] as HTMLElement; - downloadButton.addEventListener('click', event => { + const downloadBtnRef = ref() + this.footer!.appendChild( + + ); + + const downloadButton = downloadBtnRef.value!; + downloadButton.addEventListener('click', _event => { const data = this.textElem.textContent!; downloadString(data, 'wowsims.json'); }); diff --git a/ui/core/components/individual_sim_ui/talents_tab.ts b/ui/core/components/individual_sim_ui/talents_tab.ts index fa3d534e24..506a1994d7 100644 --- a/ui/core/components/individual_sim_ui/talents_tab.ts +++ b/ui/core/components/individual_sim_ui/talents_tab.ts @@ -114,7 +114,7 @@ export class TalentsTab extends SimTab { private buildHunterPetPicker(parentElem: HTMLElement) { new HunterPetTalentsPicker(parentElem, this.simUI, this.simUI.player as Player); - const petTypeToggle = new IconEnumPicker(parentElem, this.simUI.player as Player, makePetTypeInputConfig(false)); + new IconEnumPicker(parentElem, this.simUI.player as Player, makePetTypeInputConfig()); } private buildSavedTalentsPicker() { diff --git a/ui/core/talents/glyphs_picker.ts b/ui/core/talents/glyphs_picker.tsx similarity index 96% rename from ui/core/talents/glyphs_picker.ts rename to ui/core/talents/glyphs_picker.tsx index d6f8e5523e..1d5113e673 100644 --- a/ui/core/talents/glyphs_picker.ts +++ b/ui/core/talents/glyphs_picker.tsx @@ -1,3 +1,6 @@ +// eslint-disable-next-line unused-imports/no-unused-imports +import { element } from 'tsx-vanilla'; + import { Glyphs } from '../proto/common.js'; import { ItemQuality } from '../proto/common.js'; import { ActionId } from '../proto_utils/action_id.js'; @@ -48,6 +51,10 @@ export class GlyphsPicker extends Component { super(parent, 'glyphs-picker-root'); this.glyphsConfig = glyphsConfig; + this.rootElem.appendChild( +
Glyphs
+ ) + const majorGlyphs = Object.keys(glyphsConfig.majorGlyphs).map(idStr => Number(idStr)); const minorGlyphs = Object.keys(glyphsConfig.minorGlyphs).map(idStr => Number(idStr)); @@ -116,7 +123,7 @@ class GlyphPicker extends Input, number> { this.iconElem = this.rootElem.getElementsByClassName('glyph-picker-icon')[0] as HTMLAnchorElement; this.iconElem.addEventListener('click', event => { event.preventDefault(); - const selectorModal = new GlyphSelectorModal(this.rootElem.closest('.individual-sim-ui')!, this, this.glyphOptions); + new GlyphSelectorModal(this.rootElem.closest('.individual-sim-ui')!, this, this.glyphOptions); }); this.init(); @@ -199,7 +206,6 @@ class GlyphSelectorModal extends BaseModal { const applyFilters = () => { let validItemElems = listItemElems; - const selectedGlyph = glyphPicker.selectedGlyph; validItemElems = validItemElems.filter(elem => { const listItemIdx = parseInt(elem.dataset.idx!); diff --git a/ui/core/talents/hunter_pet.ts b/ui/core/talents/hunter_pet.ts index 21e89b37ba..f6ae9a52a2 100644 --- a/ui/core/talents/hunter_pet.ts +++ b/ui/core/talents/hunter_pet.ts @@ -2,7 +2,6 @@ import { Spec } from '../proto/common.js'; import { HunterPetTalents, Hunter_Options_PetType as PetType } from '../proto/hunter.js'; import { Player } from '../player.js'; import { Component } from '../components/component.js'; -import { EnumPicker, EnumPickerConfig } from '../components/enum_picker.js'; import { SavedDataManager } from '../components/saved_data_manager.js'; import { EventID, TypedEvent } from '../typed_event.js'; import { ActionId } from '../proto_utils/action_id.js'; @@ -17,11 +16,10 @@ import HunterPetCunningJson from './trees/hunter_cunning.json' import HunterPetFerocityJson from './trees/hunter_ferocity.json' import HunterPetTenacityJson from './trees/hunter_tenacity.json' -export function makePetTypeInputConfig(includeLabel: boolean): InputHelpers.TypedIconEnumPickerConfig, PetType> { +export function makePetTypeInputConfig(): InputHelpers.TypedIconEnumPickerConfig, PetType> { return InputHelpers.makeSpecOptionsEnumIconInput({ fieldName: 'petType', numColumns: 5, - //label: includeLabel ? 'Pet' : '', values: [ { value: PetType.PetNone, tooltip: 'No Pet' }, { actionId: ActionId.fromPetName('Bat'), tooltip: 'Bat', value: PetType.Bat }, @@ -142,7 +140,7 @@ export class HunterPetTalentsPicker extends Component { klass: player.getClass(), trees: talentsConfig, changedEvent: (player: Player) => player.specOptionsChangeEmitter, - getValue: (player: Player) => protoToTalentString(this.getPetTalentsFromPlayer(), talentsConfig), + getValue: (_player: Player) => protoToTalentString(this.getPetTalentsFromPlayer(), talentsConfig), setValue: (eventID: EventID, player: Player, newValue: string) => { const options = player.getSpecOptions(); options.petTalents = talentStringToProto(HunterPetTalents.create(), newValue, talentsConfig); @@ -159,7 +157,7 @@ export class HunterPetTalentsPicker extends Component { presetsOnly: true, label: 'Pet Talents', storageKey: '__NEVER_USED__', - getData: (player: Player) => protoToTalentString(this.getPetTalentsFromPlayer(), talentsConfig), + getData: (_player: Player) => protoToTalentString(this.getPetTalentsFromPlayer(), talentsConfig), setData: (eventID: EventID, player: Player, newValue: string) => { const options = player.getSpecOptions(); options.petTalents = talentStringToProto(HunterPetTalents.create(), newValue, talentsConfig); @@ -171,7 +169,7 @@ export class HunterPetTalentsPicker extends Component { changeEmitters: [this.player.specOptionsChangeEmitter], equals: (a: string, b: string) => a == b, toJson: (a: string) => a, - fromJson: (obj: any) => '', + fromJson: (_obj: any) => '', }); savedTalentsManager.addSavedData({ name: 'Default', diff --git a/ui/core/talents/talents_picker.tsx b/ui/core/talents/talents_picker.tsx index d347c2def7..010f8d4a3c 100644 --- a/ui/core/talents/talents_picker.tsx +++ b/ui/core/talents/talents_picker.tsx @@ -1,5 +1,9 @@ -import { Carousel, Tooltip } from 'bootstrap'; +// eslint-disable-next-line unused-imports/no-unused-imports +import { element, fragment, ref } from 'tsx-vanilla'; +import { Tooltip } from 'bootstrap'; + import { Component } from '../components/component.js'; +import { CopyButton } from '../components/copy_button.js'; import { Input, InputConfig } from '../components/input.js'; import { Class, Spec } from '../proto/common.js'; import { ActionId } from '../proto_utils/action_id.js'; @@ -9,21 +13,19 @@ import { isRightClick } from '../utils.js'; import { sum } from '../utils.js'; import { Player } from '../player.js'; -import { element, fragment } from 'tsx-vanilla'; - const MAX_POINTS_PLAYER = 71; const MAX_POINTS_HUNTER_PET = 16; const MAX_POINTS_HUNTER_PET_BM = 20; -export interface TalentsPickerConfig extends InputConfig { +export interface TalentsPickerConfig extends InputConfig, string> { klass: Class, - trees: TalentsConfig, - pointsPerRow: number, maxPoints: number, + pointsPerRow: number, + trees: TalentsConfig, } -export class TalentsPicker extends Input { - private readonly config: TalentsPickerConfig; +export class TalentsPicker extends Input, string> { + private readonly config: TalentsPickerConfig; readonly numRows: number; readonly numCols: number; @@ -32,29 +34,58 @@ export class TalentsPicker extends Input>; - constructor(parent: HTMLElement, modObject: ModObject, config: TalentsPickerConfig) { - super(parent, 'talents-picker-root', modObject, { ...config, inline: true }); + constructor(parent: HTMLElement, player: Player, config: TalentsPickerConfig) { + super(parent, 'talents-picker-root', player, { ...config, inline: true }); this.config = config; this.pointsPerRow = config.pointsPerRow; - this.maxPoints = config.maxPoints; this.numRows = Math.max(...config.trees.map(treeConfig => treeConfig.talents.map(talentConfig => talentConfig.location.rowIdx).flat()).flat()) + 1; this.numCols = Math.max(...config.trees.map(treeConfig => treeConfig.talents.map(talentConfig => talentConfig.location.colIdx).flat()).flat()) + 1; + this.maxPoints = config.maxPoints + + const pointsRemainingElemRef = ref(); + const getPointsRemaining = () => this.maxPoints - player.getTalentTreePoints().reduce((sum, points) => sum + points, 0); + + const PointsRemainingElem = () => { + const pointsRemaining = getPointsRemaining(); + return {pointsRemaining} + } + TypedEvent.onAny([player.talentsChangeEmitter]).on(() => { + pointsRemainingElemRef.value!.replaceWith(PointsRemainingElem()) + }); + + const actionsContainerRef = ref(); this.rootElem.appendChild( -