diff --git a/ui/core/components/importers.ts b/ui/core/components/importers.tsx similarity index 82% rename from ui/core/components/importers.ts rename to ui/core/components/importers.tsx index 303eefe341..a204096ce3 100644 --- a/ui/core/components/importers.ts +++ b/ui/core/components/importers.tsx @@ -1,5 +1,6 @@ import { JsonObject } from '@protobuf-ts/runtime'; import { default as pako } from 'pako'; +import { ref } from 'tsx-vanilla'; import { IndividualSimUI } from '../individual_sim_ui'; import { Class, EquipmentSpec, Glyphs, ItemSlot, ItemSpec, Profession, Race, Spec } from '../proto/common'; @@ -26,40 +27,46 @@ export abstract class Importer extends BaseModal { this.includeFile = includeFile; const uploadInputId = 'upload-input-' + title.toLowerCase().replaceAll(' ', '-'); - this.body.innerHTML = ` -
- - `; - this.footer!.innerHTML = ` - ${ - this.includeFile - ? ` - - - ` - : '' - } - - `; - - this.textElem = this.rootElem.getElementsByClassName('importer-textarea')[0] as HTMLTextAreaElement; - this.descriptionElem = this.rootElem.getElementsByClassName('import-description')[0] as HTMLElement; - - if (this.includeFile) { - const uploadInput = this.rootElem.getElementsByClassName('importer-upload-input')[0] as HTMLButtonElement; - uploadInput.addEventListener('change', async event => { - const data: string = await (event as any).target.files[0].text(); - this.textElem.textContent = data; + const descriptionElemRef = ref(); + const textElemRef = ref(); + const importButtonRef = ref(); + const uploadInputRef = ref(); + + this.body.replaceChildren( + <> +
+ + , + ); + + this.footer!.appendChild( + <> + {this.includeFile && ( + <> + + + + )} + + , + ); + + this.descriptionElem = descriptionElemRef.value!; + this.textElem = textElemRef.value!; + + if (this.includeFile && uploadInputRef.value) { + uploadInputRef.value.addEventListener('change', async event => { + this.textElem.textContent = await (event as any).target.files[0].text(); }); } - this.importButton = this.rootElem.getElementsByClassName('import-button')[0] as HTMLButtonElement; + this.importButton = importButtonRef.value!; this.importButton.addEventListener('click', async _event => { try { await this.onImport(this.textElem.value || ''); @@ -193,10 +200,12 @@ export class IndividualJsonImporter extends Importer { super(parent, simUI, 'JSON Import', true); this.simUI = simUI; - this.descriptionElem.innerHTML = ` -

Import settings from a JSON file, which can be created using the JSON Export feature.

-

To import, upload the file or paste the text below, then click, 'Import'.

- `; + this.descriptionElem.appendChild( + <> +

Import settings from a JSON file, which can be created using the JSON Export feature.

+

To import, upload the file or paste the text below, then click, 'Import'.

+ , + ); } async onImport(data: string) { @@ -220,23 +229,25 @@ export class IndividualJsonImporter extends Importer { } } -export class Individual80UImporter extends Importer { +export class Individual60UImporter extends Importer { private readonly simUI: IndividualSimUI; constructor(parent: HTMLElement, simUI: IndividualSimUI) { super(parent, simUI, '60 Upgrades Cataclysm Import', true); this.simUI = simUI; - this.descriptionElem.innerHTML = ` -

- Import settings from 60 Upgrades. -

-

- This feature imports gear, race, and (optionally) talents. It does NOT import buffs, debuffs, consumes, rotation, or custom stats. -

-

- To import, paste the output from the site's export option below and click, 'Import'. -

- `; + this.descriptionElem.appendChild( + <> +

+ Import settings from{' '} + + 60 Upgrades + + . +

+

This feature imports gear, race, and (optionally) talents. It does NOT import buffs, debuffs, consumes, rotation, or custom stats.

+

To import, paste the output from the site's export option below and click, 'Import'.

+ , + ); } async onImport(data: string) { @@ -244,7 +255,7 @@ export class Individual80UImporter extends Importer { try { importJson = JSON.parse(data); } catch { - throw new Error('Please use a valid 80U export.'); + throw new Error('Please use a valid 60U export.'); } // Parse all the settings. @@ -274,6 +285,10 @@ export class Individual80UImporter extends Importer { if (itemJson.gems) { itemSpec.gems = (itemJson.gems as Array).filter(gemJson => gemJson?.id).map(gemJson => gemJson.id); } + if (itemJson.reforge?.id) { + itemSpec.reforging = itemJson.reforge.id; + } + equipmentSpec.items.push(itemSpec); }); @@ -289,17 +304,19 @@ export class IndividualWowheadGearPlannerImporter extends super(parent, simUI, 'Wowhead Import', true); this.simUI = simUI; - this.descriptionElem.innerHTML = ` -

- Import settings from Wowhead Gear Planner. -

-

- This feature imports gear, race, and (optionally) talents. It does NOT import buffs, debuffs, consumes, rotation, or custom stats. -

-

- To import, paste the gear planner link below and click, 'Import'. -

- `; + this.descriptionElem.appendChild( + <> +

+ Import settings from{' '} + + Wowhead Gear Planner + + . +

+

This feature imports gear, race, and (optionally) talents. It does NOT import buffs, debuffs, consumes, rotation, or custom stats.

+

To import, paste the gear planner link below and click, 'Import'.

+ , + ); } async onImport(url: string) { @@ -475,17 +492,41 @@ export class IndividualAddonImporter extends Importer { super(parent, simUI, 'Addon Import', true); this.simUI = simUI; - this.descriptionElem.innerHTML = ` -

- Import settings from the WoWSims Importer In-Game Addon. -

-

- This feature imports gear, race, talents, glyphs, and professions. It does NOT import buffs, debuffs, consumes, rotation, or custom stats. -

-

- To import, paste the output from the addon below and click, 'Import'. -

- `; + const warningRef = ref(); + this.descriptionElem.appendChild( + <> +

+ Import settings from the{' '} + + WoWSims Importer In-Game Addon + + . +

+

+ This feature imports gear, race, talents, glyphs, and professions. It does NOT import buffs, debuffs, consumes, rotation, or custom stats. +

+

To import, paste the output from the addon below and click, 'Import'.

+
+ , + ); + + if (warningRef.value) + new Toast({ + title: 'Reforging issues', + body: ( + <> + There are known issues with Reforging when using the WSE addon. +
+ Always make sure to double check your reforges after importing. + + ), + additionalClasses: ['toast-import-warning'], + container: warningRef.value, + variant: 'warning', + canClose: false, + autoShow: true, + autohide: false, + }); } async onImport(data: string) { diff --git a/ui/core/components/toast.tsx b/ui/core/components/toast.tsx index 928fbb2076..de819e9907 100644 --- a/ui/core/components/toast.tsx +++ b/ui/core/components/toast.tsx @@ -1,26 +1,34 @@ import { Toast as BootstrapToast } from 'bootstrap'; +import clsx from 'clsx'; type ToastOptions = { title?: string; - variant: 'info' | 'success' | 'error'; + variant: 'info' | 'success' | 'error' | 'warning'; body: string | Element; autoShow?: boolean; + canClose?: boolean; + container?: Element; + additionalClasses?: string[]; } & Partial; class Toast { - private element: HTMLElement; - private container: HTMLElement; + readonly element: HTMLElement; + private container: Element; private title: ToastOptions['title']; private body: ToastOptions['body']; private variant: ToastOptions['variant']; + private canClose: ToastOptions['canClose']; + private additionalClasses: ToastOptions['additionalClasses']; public instance; constructor(options: ToastOptions) { - const { title, variant, autoShow = true, body, ...bootstrapOptions } = options || {}; - this.container = document.getElementById('toastContainer')!; + const { title, variant, autoShow = true, canClose = true, body, additionalClasses, container, ...bootstrapOptions } = options || {}; + this.container = container || document.getElementById('toastContainer')!; + this.additionalClasses = additionalClasses; this.title = title || 'WowSims'; this.variant = variant || 'info'; this.body = body; + this.canClose = canClose; this.element = this.template(); this.container.appendChild(this.element); @@ -58,13 +66,15 @@ class Toast { return 'fa-check-circle'; case 'error': return 'fa-exclamation-circle'; + case 'warning': + return 'fa-triangle-exclamation'; } } template() { return (
{this.title} - - - + {this.canClose && ( + + + + )}
{this.body}
diff --git a/ui/core/individual_sim_ui.tsx b/ui/core/individual_sim_ui.tsx index 77b757c5c4..243ca2eb94 100644 --- a/ui/core/individual_sim_ui.tsx +++ b/ui/core/individual_sim_ui.tsx @@ -434,7 +434,7 @@ export abstract class IndividualSimUI extends SimUI { private addTopbarComponents() { this.simHeader.addImportLink('JSON', new Importers.IndividualJsonImporter(this.rootElem, this), true); - this.simHeader.addImportLink('60U Cata', new Importers.Individual80UImporter(this.rootElem, this), true); + this.simHeader.addImportLink('60U Cata', new Importers.Individual60UImporter(this.rootElem, this), true); this.simHeader.addImportLink('WoWHead', new Importers.IndividualWowheadGearPlannerImporter(this.rootElem, this), false); this.simHeader.addImportLink('Addon', new Importers.IndividualAddonImporter(this.rootElem, this), true); diff --git a/ui/scss/core/components/_importers.scss b/ui/scss/core/components/_importers.scss index 199647de3b..4d3abd0052 100644 --- a/ui/scss/core/components/_importers.scss +++ b/ui/scss/core/components/_importers.scss @@ -10,4 +10,9 @@ width: 12rem; } } + + .toast-import-warning { + width: 100%; + margin-bottom: var(--spacer-3); + } }