From 1a345d88c858316ff14276ef2bb61af4c7a5b4ff Mon Sep 17 00:00:00 2001 From: Kayla Glick <12898988+kayla-glick@users.noreply.github.com> Date: Thu, 14 Mar 2024 00:34:51 -0400 Subject: [PATCH] add social links to sidebar (#4235) --- .prettierrc.js | 2 +- ui/core/components/sim_header.tsx | 178 ++++---- ui/core/components/social_links.tsx | 49 +++ ui/core/individual_sim_ui.ts | 388 +++++++++++------- ui/core/sim_ui.ts | 176 ++++---- .../individual_sim_ui/_gem_summary.scss | 45 +- ui/scss/core/sim_ui/_sidebar.scss | 152 ++++--- ui/scss/shared/_variables.scss | 141 +++---- 8 files changed, 656 insertions(+), 475 deletions(-) create mode 100644 ui/core/components/social_links.tsx diff --git a/.prettierrc.js b/.prettierrc.js index 728fc1eba7..15f84e168f 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -2,7 +2,7 @@ * @type {import("prettier").Options)} */ module.exports = { - printWidth: 100, + printWidth: 160, useTabs: true, tabWidth: 4, semi: true, diff --git a/ui/core/components/sim_header.tsx b/ui/core/components/sim_header.tsx index a9f2f025d9..740ff869df 100644 --- a/ui/core/components/sim_header.tsx +++ b/ui/core/components/sim_header.tsx @@ -1,19 +1,21 @@ +import { Tooltip } from 'bootstrap'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { element } from 'tsx-vanilla'; + +import { SimUI } from '../sim_ui'; import { Component } from './component'; import { SettingsMenu } from './settings_menu'; -import { SimUI } from '../sim_ui'; -import { Tooltip } from 'bootstrap'; import { SimTab } from './sim_tab'; - -import { element, fragment } from 'tsx-vanilla'; +import { SocialLinks } from './social_links'; interface ToolbarLinkArgs { - parent: HTMLElement, - href?: string, - text?: string, - icon?: string, - tooltip?: string | HTMLElement, - classes?: string, - onclick?: Function + parent: HTMLElement; + href?: string; + text?: string; + icon?: string; + tooltip?: string | HTMLElement; + classes?: string; + onclick?: () => void; } export class SimHeader extends Component { @@ -30,7 +32,7 @@ export class SimHeader extends Component { this.simTabsContainer = this.rootElem.querySelector('.sim-tabs') as HTMLElement; this.simToolbar = this.rootElem.querySelector('.sim-toolbar') as HTMLElement; - this.knownIssuesContent = as HTMLElement; + this.knownIssuesContent = () as HTMLElement; this.knownIssuesLink = this.addKnownIssuesLink(); this.addBugReportLink(); this.addDownloadBinaryLink(); @@ -40,8 +42,8 @@ export class SimHeader extends Component { // Allow styling the sticky header new IntersectionObserver( ([e]) => e.target.classList.toggle('stuck', e.intersectionRatio < 1), - {threshold: [1]} - ).observe(this.rootElem) + { threshold: [1] }, + ).observe(this.rootElem); } activateTab(className: string) { @@ -51,21 +53,21 @@ export class SimHeader extends Component { addTab(title: string, contentId: string) { const isFirstTab = this.simTabsContainer.children.length == 0; - let classes = `${contentId} nav-item`; - let tab = ( -
  • - + - {title} + type="button"> + {title}
  • ); @@ -90,16 +92,22 @@ export class SimHeader extends Component { addExportLink(label: string, onClick: (parent: HTMLElement) => void, hideInRaidSim?: boolean) { this.addImportExportLink('export-dropdown', label, onClick, hideInRaidSim); } - private addImportExportLink(cssClass: string, label: string, onClick: (parent: HTMLElement) => void, hideInRaidSim?: boolean) { + private addImportExportLink( + cssClass: string, + label: string, + onClick: (parent: HTMLElement) => void, + hideInRaidSim?: boolean, + ) { const dropdownElem = this.rootElem.getElementsByClassName(cssClass)[0] as HTMLElement; const menuElem = dropdownElem.getElementsByClassName('dropdown-menu')[0] as HTMLElement; const itemElem = (
  • - {label} @@ -112,25 +120,24 @@ export class SimHeader extends Component { } private addToolbarLink(args: ToolbarLinkArgs): HTMLElement { - let item = ( + const item = (
    - + target={args.href ? '_blank' : '_self'}> {args.icon && } - {args.text ? ` ${args.text} ` : ''} + {args.text ? ` ${args.text} ` : ''} -
    ); + + ); - let link = item.children[0]; + const link = item.children[0]; if (args.onclick) { link.addEventListener('click', () => { // Typescript is requiring this even though the condition is being done already above - if (args.onclick) - args.onclick(); + if (args.onclick) args.onclick(); }); } @@ -148,44 +155,46 @@ export class SimHeader extends Component { private addKnownIssuesLink(): HTMLElement { return this.addToolbarLink({ parent: this.simToolbar, - text: "Known Issues", + text: 'Known Issues', tooltip: this.knownIssuesContent, - classes: "known-issues link-danger hide" + classes: 'known-issues link-danger hide', }).children[0] as HTMLElement; } addKnownIssue(issue: string) { this.knownIssuesContent.appendChild(
  • {issue}
  • ); this.knownIssuesLink.classList.remove('hide'); - Tooltip.getInstance(this.knownIssuesLink)?.setContent({'.tooltip-inner': this.knownIssuesContent}); + Tooltip.getInstance(this.knownIssuesLink)?.setContent({ + '.tooltip-inner': this.knownIssuesContent, + }); } private addBugReportLink() { this.addToolbarLink({ - href: "https://github.com/wowsims/wotlk/issues/new/choose", + href: 'https://github.com/wowsims/wotlk/issues/new/choose', parent: this.simToolbar, - icon: "fas fa-bug fa-lg", - tooltip: "Report a bug or
    Request a feature" - }) + icon: 'fas fa-bug fa-lg', + tooltip: 'Report a bug or
    Request a feature', + }); } private addDownloadBinaryLink() { - let href = "https://github.com/wowsims/wotlk/releases"; - let icon = "fas fa-gauge-high fa-lg" - let parent = this.simToolbar; + const href = 'https://github.com/wowsims/wotlk/releases'; + const icon = 'fas fa-gauge-high fa-lg'; + const parent = this.simToolbar; - if (document.location.href.includes("localhost")) { - fetch("/version").then(resp => { + if (document.location.href.includes('localhost')) { + fetch('/version').then(resp => { resp.json() - .then((versionInfo) => { + .then(versionInfo => { if (versionInfo.outdated == 2) { this.addToolbarLink({ href: href, parent: parent, icon: icon, - tooltip: "Newer version of simulator available for download", - classes: "downbin link-danger", - }) + tooltip: 'Newer version of simulator available for download', + classes: 'downbin link-danger', + }); } }) .catch(error => { @@ -197,25 +206,25 @@ export class SimHeader extends Component { href: href, parent: parent, icon: icon, - tooltip: "Download simulator for faster simulating", - classes: "downbin", - }) + tooltip: 'Download simulator for faster simulating', + classes: 'downbin', + }); } } private addSimOptionsLink() { this.addToolbarLink({ parent: this.simToolbar, - icon: "fas fa-cog fa-lg", - tooltip: "Show Sim Options", + icon: 'fas fa-cog fa-lg', + tooltip: 'Show Sim Options', classes: 'sim-options', - onclick: () => new SettingsMenu(this.simUI.rootElem, this.simUI) - }) + onclick: () => new SettingsMenu(this.simUI.rootElem, this.simUI), + }); } private addSocialLinks() { - let container = document.createElement('div'); - container.classList.add('sim-toolbar-socials') + const container = document.createElement('div'); + container.classList.add('sim-toolbar-socials'); this.simToolbar.appendChild(container); this.addDiscordLink(container); @@ -224,51 +233,46 @@ export class SimHeader extends Component { } private addDiscordLink(container: HTMLElement) { - this.addToolbarLink({ - href: "https://discord.gg/p3DgvmnDCS", - parent: container, - icon: "fab fa-discord fa-lg", - tooltip: "Join us on Discord", - classes: "discord-link link-alt" - }) + container.appendChild( +
    {SocialLinks.buildDiscordLink()}
    , + ); } private addGitHubLink(container: HTMLElement) { - this.addToolbarLink({ - href: "https://github.com/wowsims/wotlk", - parent: container, - icon: "fab fa-github fa-lg", - tooltip: "Contribute on GitHub", - classes: "github-link link-alt" - }) + container.appendChild( +
    {SocialLinks.buildGitHubLink()}
    , + ); } private addPatreonLink(container: HTMLElement) { - this.addToolbarLink({ - href: "https://patreon.com/wowsims", - parent: container, - text: "Support our devs", - icon: "fab fa-patreon fa-lg", - tooltip: "Support us on Patreon", - classes: "patreon-link link-alt", - }) + container.appendChild( +
    {SocialLinks.buildPatreonLink()}
    , + ); } protected customRootElement(): HTMLElement { return (
    - +
    - + {' Import '}
      - + {' Export '} diff --git a/ui/core/components/social_links.tsx b/ui/core/components/social_links.tsx new file mode 100644 index 0000000000..d586df3c6f --- /dev/null +++ b/ui/core/components/social_links.tsx @@ -0,0 +1,49 @@ +import { Tooltip } from 'bootstrap'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { element } from 'tsx-vanilla'; + +import { Component } from './component'; + +export class SocialLinks extends Component { + static buildDiscordLink(): Element { + const anchor = ( + + + + ); + Tooltip.getOrCreateInstance(anchor); + return anchor; + } + + static buildGitHubLink(): Element { + const anchor = ( + + + + ); + Tooltip.getOrCreateInstance(anchor); + return anchor; + } + + static buildPatreonLink(): Element { + const anchor = ( + + Patreon + + ); + Tooltip.getOrCreateInstance(anchor); + return anchor; + } +} diff --git a/ui/core/individual_sim_ui.ts b/ui/core/individual_sim_ui.ts index 11cd1d43f5..2195e3e5b8 100644 --- a/ui/core/individual_sim_ui.ts +++ b/ui/core/individual_sim_ui.ts @@ -1,22 +1,25 @@ -import { simLaunchStatuses } from './launched_sims'; -import { Player, PlayerConfig, registerSpecConfig as registerPlayerConfig } from './player'; -import { SimUI, SimWarning } from './sim_ui'; -import { EventID, TypedEvent } from './typed_event'; - import { CharacterStats, StatMods } from './components/character_stats'; import { ContentBlock } from './components/content_block'; import { EmbeddedDetailedResults } from './components/detailed_results'; import { EncounterPickerConfig } from './components/encounter_picker'; -import { addRaidSimAction, RaidSimResultsManager } from './components/raid_sim_action'; -import { SavedDataConfig } from './components/saved_data_manager'; -import { addStatWeightsAction } from './components/stat_weights_action'; - +import * as Exporters from './components/exporters'; +import * as IconInputs from './components/icon_inputs'; +import * as Importers from './components/importers'; import { BulkTab } from './components/individual_sim_ui/bulk_tab'; import { GearTab } from './components/individual_sim_ui/gear_tab'; -import { SettingsTab } from './components/individual_sim_ui/settings_tab'; import { RotationTab } from './components/individual_sim_ui/rotation_tab'; +import { SettingsTab } from './components/individual_sim_ui/settings_tab'; import { TalentsTab } from './components/individual_sim_ui/talents_tab'; - +import * as InputHelpers from './components/input_helpers'; +import { addRaidSimAction, RaidSimResultsManager } from './components/raid_sim_action'; +import { SavedDataConfig } from './components/saved_data_manager'; +import { addStatWeightsAction } from './components/stat_weights_action'; +import * as Mechanics from './constants/mechanics'; +import * as Tooltips from './constants/tooltips'; +import { simLaunchStatuses } from './launched_sims'; +import { Player, PlayerConfig, registerSpecConfig as registerPlayerConfig } from './player'; +import { PresetGear, PresetRotation } from './preset_utils'; +import { StatWeightsResult } from './proto/api'; import { Consumes, Debuffs, @@ -35,10 +38,7 @@ import { Spec, Stat, } from './proto/common'; - import { IndividualSimSettings, SavedTalents } from './proto/ui'; -import { StatWeightsResult } from './proto/api'; - import { getMetaGemConditionDescription } from './proto_utils/gems'; import { professionNames } from './proto_utils/names'; import { Stats } from './proto_utils/stats'; @@ -50,126 +50,123 @@ import { specToEligibleRaces, specToLocalStorageKey, } from './proto_utils/utils'; - -import {PresetGear, PresetRotation} from './preset_utils'; - -import * as Exporters from './components/exporters'; -import * as Importers from './components/importers'; -import * as IconInputs from './components/icon_inputs'; -import * as InputHelpers from './components/input_helpers'; -import * as Mechanics from './constants/mechanics'; -import * as Tooltips from './constants/tooltips'; import { SimSettingCategories } from './sim'; +import { SimUI, SimWarning } from './sim_ui'; +import { EventID, TypedEvent } from './typed_event'; const SAVED_GEAR_STORAGE_KEY = '__savedGear__'; const SAVED_ROTATION_STORAGE_KEY = '__savedRotation__'; const SAVED_SETTINGS_STORAGE_KEY = '__savedSettings__'; const SAVED_TALENTS_STORAGE_KEY = '__savedTalents__'; -export type InputConfig = ( - InputHelpers.TypedBooleanPickerConfig | - InputHelpers.TypedNumberPickerConfig | - InputHelpers.TypedEnumPickerConfig -); +export type InputConfig = + | InputHelpers.TypedBooleanPickerConfig + | InputHelpers.TypedNumberPickerConfig + | InputHelpers.TypedEnumPickerConfig; export interface InputSection { - tooltip?: string, - inputs: Array>>, + tooltip?: string; + inputs: Array>>; } export interface OtherDefaults { - profession1?: Profession, - profession2?: Profession, - distanceFromTarget?: number, - channelClipDelay?: number, - nibelungAverageCasts?: number, + profession1?: Profession; + profession2?: Profession; + distanceFromTarget?: number; + channelClipDelay?: number; + nibelungAverageCasts?: number; } export interface RaidSimPreset { - spec: Spec, - talents: SavedTalents, - specOptions: SpecOptions, - consumes: Consumes, - - defaultName: string, - defaultFactionRaces: Record, - defaultGear: Record>, - otherDefaults?: OtherDefaults, - - tooltip: string, - iconUrl: string, + spec: Spec; + talents: SavedTalents; + specOptions: SpecOptions; + consumes: Consumes; + + defaultName: string; + defaultFactionRaces: Record; + defaultGear: Record>; + otherDefaults?: OtherDefaults; + + tooltip: string; + iconUrl: string; } export interface IndividualSimUIConfig extends PlayerConfig { // Additional css class to add to the root element. - cssClass: string, + cssClass: string; // Used to generate schemed components. E.g. 'shaman', 'druid', 'raid' - cssScheme: string, + cssScheme: string; knownIssues?: Array; - warnings?: Array<(simUI: IndividualSimUI) => SimWarning>, + warnings?: Array<(simUI: IndividualSimUI) => SimWarning>; epStats: Array; epPseudoStats?: Array; epReferenceStat: Stat; displayStats: Array; - modifyDisplayStats?: (player: Player) => StatMods, + modifyDisplayStats?: (player: Player) => StatMods; defaults: { - gear: EquipmentSpec, - epWeights: Stats, - consumes: Consumes, - talents: SavedTalents, - specOptions: SpecOptions, + gear: EquipmentSpec; + epWeights: Stats; + consumes: Consumes; + talents: SavedTalents; + specOptions: SpecOptions; - raidBuffs: RaidBuffs, - partyBuffs: PartyBuffs, - individualBuffs: IndividualBuffs, + raidBuffs: RaidBuffs; + partyBuffs: PartyBuffs; + individualBuffs: IndividualBuffs; - debuffs: Debuffs, + debuffs: Debuffs; - other?: OtherDefaults, - }, + other?: OtherDefaults; + }; - playerInputs?: InputSection, - playerIconInputs: Array, any>>, - petConsumeInputs?: Array, any>>, + playerInputs?: InputSection; + playerIconInputs: Array, any>>; + petConsumeInputs?: Array, any>>; rotationInputs?: InputSection; rotationIconInputs?: Array, any>>; - includeBuffDebuffInputs: Array, - excludeBuffDebuffInputs: Array, + includeBuffDebuffInputs: Array; + excludeBuffDebuffInputs: Array; otherInputs: InputSection; // Currently, many classes don't support item swapping, and only in certain slots. // So enable it only where it is supported. - itemSwapSlots?: Array, + itemSwapSlots?: Array; // For when extra sections are needed (e.g. Shaman totems) - customSections?: Array<(parentElem: HTMLElement, simUI: IndividualSimUI) => ContentBlock>, + customSections?: Array< + (parentElem: HTMLElement, simUI: IndividualSimUI) => ContentBlock + >; - encounterPicker: EncounterPickerConfig, + encounterPicker: EncounterPickerConfig; presets: { - gear: Array, - talents: Array, SavedTalents>>, - rotations: Array, - }, + gear: Array; + talents: Array, SavedTalents>>; + rotations: Array; + }; - raidSimPresets: Array>, + raidSimPresets: Array>; } -export function registerSpecConfig(spec: SpecType, config: IndividualSimUIConfig): IndividualSimUIConfig { +export function registerSpecConfig( + spec: SpecType, + config: IndividualSimUIConfig, +): IndividualSimUIConfig { registerPlayerConfig(spec, config); return config; } -export let itemSwapEnabledSpecs: Array = []; +export const itemSwapEnabledSpecs: Array = []; export interface Settings { - raidBuffs: RaidBuffs, - partyBuffs: PartyBuffs, - individualBuffs: IndividualBuffs, - consumes: Consumes, - race: Race, + raidBuffs: RaidBuffs; + partyBuffs: PartyBuffs; + individualBuffs: IndividualBuffs; + consumes: Consumes; + race: Race; professions?: Array; } @@ -188,7 +185,11 @@ export abstract class IndividualSimUI extends SimUI { readonly bt: BulkTab; - constructor(parentElem: HTMLElement, player: Player, config: IndividualSimUIConfig) { + constructor( + parentElem: HTMLElement, + player: Player, + config: IndividualSimUIConfig, + ) { super(parentElem, player.sim, { cssClass: config.cssClass, cssScheme: config.cssScheme, @@ -203,7 +204,10 @@ export abstract class IndividualSimUI extends SimUI { this.prevEpIterations = 0; this.prevEpSimResult = null; - if ((config.itemSwapSlots || []).length > 0 && !itemSwapEnabledSpecs.includes(player.spec)) { + if ( + (config.itemSwapSlots || []).length > 0 && + !itemSwapEnabledSpecs.includes(player.spec) + ) { itemSwapEnabledSpecs.push(player.spec); } @@ -215,18 +219,30 @@ export abstract class IndividualSimUI extends SimUI { } const metaGem = this.player.getGear().getMetaGem()!; - return `Meta gem disabled (${metaGem.name}): ${getMetaGemConditionDescription(metaGem)}`; + return `Meta gem disabled (${metaGem.name}): ${getMetaGemConditionDescription( + metaGem, + )}`; }, }); this.addWarning({ - updateOn: TypedEvent.onAny([this.player.gearChangeEmitter, this.player.professionChangeEmitter]), + updateOn: TypedEvent.onAny([ + this.player.gearChangeEmitter, + this.player.professionChangeEmitter, + ]), getContent: () => { - const failedProfReqs = this.player.getGear().getFailedProfessionRequirements(this.player.getProfessions()); + const failedProfReqs = this.player + .getGear() + .getFailedProfessionRequirements(this.player.getProfessions()); if (failedProfReqs.length == 0) { return ''; } - return failedProfReqs.map(fpr => `${fpr.name} requires ${professionNames.get(fpr.requiredProfession)!}, but it is not selected.`); + return failedProfReqs.map( + fpr => + `${fpr.name} requires ${professionNames.get( + fpr.requiredProfession, + )!}, but it is not selected.`, + ); }, }); this.addWarning({ @@ -258,13 +274,20 @@ export abstract class IndividualSimUI extends SimUI { }, }); this.addWarning({ - updateOn: TypedEvent.onAny([this.player.gearChangeEmitter, this.player.talentsChangeEmitter]), + updateOn: TypedEvent.onAny([ + this.player.gearChangeEmitter, + this.player.talentsChangeEmitter, + ]), getContent: () => { - if (!this.player.canDualWield2H() && - (this.player.getEquippedItem(ItemSlot.ItemSlotMainHand)?.item.handType == HandType.HandTypeTwoHand && - this.player.getEquippedItem(ItemSlot.ItemSlotOffHand) != null || - this.player.getEquippedItem(ItemSlot.ItemSlotOffHand)?.item.handType == HandType.HandTypeTwoHand)) { - return "Dual wielding two-handed weapon(s) without Titan's Grip spec." + if ( + !this.player.canDualWield2H() && + ((this.player.getEquippedItem(ItemSlot.ItemSlotMainHand)?.item.handType == + HandType.HandTypeTwoHand && + this.player.getEquippedItem(ItemSlot.ItemSlotOffHand) != null) || + this.player.getEquippedItem(ItemSlot.ItemSlotOffHand)?.item.handType == + HandType.HandTypeTwoHand) + ) { + return "Dual wielding two-handed weapon(s) without Titan's Grip spec."; } else { return ''; } @@ -316,9 +339,15 @@ export abstract class IndividualSimUI extends SimUI { // Loading from link needs to happen after loading saved settings, so that partial link imports // (e.g. rotation only) include the previous settings for other categories. try { - const urlParseResults = Importers.IndividualLinkImporter.tryParseUrlLocation(window.location); + const urlParseResults = Importers.IndividualLinkImporter.tryParseUrlLocation( + window.location, + ); if (urlParseResults) { - this.fromProto(initEventID, urlParseResults.settings, urlParseResults.categories); + this.fromProto( + initEventID, + urlParseResults.settings, + urlParseResults.categories, + ); } } catch (e) { console.warn('Failed to parse link settings: ' + e); @@ -337,23 +366,29 @@ export abstract class IndividualSimUI extends SimUI { private addSidebarComponents() { this.raidSimResultsManager = addRaidSimAction(this); - addStatWeightsAction(this, this.individualConfig.epStats, this.individualConfig.epPseudoStats, this.individualConfig.epReferenceStat); + addStatWeightsAction( + this, + this.individualConfig.epStats, + this.individualConfig.epPseudoStats, + this.individualConfig.epReferenceStat, + ); const _characterStats = new CharacterStats( - this.rootElem.getElementsByClassName('sim-sidebar-footer')[0] as HTMLElement, + this.rootElem.querySelector('.sim-sidebar-stats') as HTMLElement, this.player, this.individualConfig.displayStats, - this.individualConfig.modifyDisplayStats); + this.individualConfig.modifyDisplayStats, + ); } private addGearTab() { - let gearTab = new GearTab(this.simTabContentsContainer, this); + const gearTab = new GearTab(this.simTabContentsContainer, this); gearTab.rootElem.classList.add('active', 'show'); } private addBulkTab(): BulkTab { - let bulkTab = new BulkTab(this.simTabContentsContainer, this); - bulkTab.navLink.hidden = !this.sim.getShowExperimental() + const bulkTab = new BulkTab(this.simTabContentsContainer, this); + bulkTab.navLink.hidden = !this.sim.getShowExperimental(); this.sim.showExperimentalChangeEmitter.on(() => { bulkTab.navLink.hidden = !this.sim.getShowExperimental(); }); @@ -373,26 +408,74 @@ export abstract class IndividualSimUI extends SimUI { } private addDetailedResultsTab() { - this.addTab('Results', 'detailed-results-tab', ` + this.addTab( + 'Results', + 'detailed-results-tab', + `
      - `); - - const _detailedResults = new EmbeddedDetailedResults(this.rootElem.getElementsByClassName('detailed-results')[0] as HTMLElement, this, this.raidSimResultsManager!); + `, + ); + + const _detailedResults = new EmbeddedDetailedResults( + this.rootElem.getElementsByClassName('detailed-results')[0] as HTMLElement, + this, + this.raidSimResultsManager!, + ); } private addTopbarComponents() { - this.simHeader.addImportLink('JSON', _parent => new Importers.IndividualJsonImporter(this.rootElem, this), true); - this.simHeader.addImportLink('80U', _parent => new Importers.Individual80UImporter(this.rootElem, this), true); - this.simHeader.addImportLink('WoWHead', _parent => new Importers.IndividualWowheadGearPlannerImporter(this.rootElem, this), false); - this.simHeader.addImportLink('Addon', _parent => new Importers.IndividualAddonImporter(this.rootElem, this), true); - - this.simHeader.addExportLink('Link', _parent => new Exporters.IndividualLinkExporter(this.rootElem, this), false); - this.simHeader.addExportLink('JSON', _parent => new Exporters.IndividualJsonExporter(this.rootElem, this), true); - this.simHeader.addExportLink('WoWHead', _parent => new Exporters.IndividualWowheadGearPlannerExporter(this.rootElem, this), false); - this.simHeader.addExportLink('80U EP', _parent => new Exporters.Individual80UEPExporter(this.rootElem, this), false); - this.simHeader.addExportLink('Pawn EP', _parent => new Exporters.IndividualPawnEPExporter(this.rootElem, this), false); - this.simHeader.addExportLink("CLI", _parent => new Exporters.IndividualCLIExporter(this.rootElem, this), true); + this.simHeader.addImportLink( + 'JSON', + _parent => new Importers.IndividualJsonImporter(this.rootElem, this), + true, + ); + this.simHeader.addImportLink( + '80U', + _parent => new Importers.Individual80UImporter(this.rootElem, this), + true, + ); + this.simHeader.addImportLink( + 'WoWHead', + _parent => new Importers.IndividualWowheadGearPlannerImporter(this.rootElem, this), + false, + ); + this.simHeader.addImportLink( + 'Addon', + _parent => new Importers.IndividualAddonImporter(this.rootElem, this), + true, + ); + + this.simHeader.addExportLink( + 'Link', + _parent => new Exporters.IndividualLinkExporter(this.rootElem, this), + false, + ); + this.simHeader.addExportLink( + 'JSON', + _parent => new Exporters.IndividualJsonExporter(this.rootElem, this), + true, + ); + this.simHeader.addExportLink( + 'WoWHead', + _parent => new Exporters.IndividualWowheadGearPlannerExporter(this.rootElem, this), + false, + ); + this.simHeader.addExportLink( + '80U EP', + _parent => new Exporters.Individual80UEPExporter(this.rootElem, this), + false, + ); + this.simHeader.addExportLink( + 'Pawn EP', + _parent => new Exporters.IndividualPawnEPExporter(this.rootElem, this), + false, + ); + this.simHeader.addExportLink( + 'CLI', + _parent => new Exporters.IndividualCLIExporter(this.rootElem, this), + true, + ); } applyDefaults(eventID: EventID) { @@ -401,28 +484,54 @@ export abstract class IndividualSimUI extends SimUI { const healingSpec = isHealingSpec(this.player.spec); //Special case for Totem of Wrath keeps buff and debuff sync'd - const towEnabled = this.individualConfig.defaults.raidBuffs.totemOfWrath || this.individualConfig.defaults.debuffs.totemOfWrath + const towEnabled = + this.individualConfig.defaults.raidBuffs.totemOfWrath || + this.individualConfig.defaults.debuffs.totemOfWrath; this.individualConfig.defaults.raidBuffs.totemOfWrath = towEnabled; this.individualConfig.defaults.debuffs.totemOfWrath = towEnabled; this.player.applySharedDefaults(eventID); this.player.setRace(eventID, specToEligibleRaces[this.player.spec][0]); - this.player.setGear(eventID, this.sim.db.lookupEquipmentSpec(this.individualConfig.defaults.gear)); + this.player.setGear( + eventID, + this.sim.db.lookupEquipmentSpec(this.individualConfig.defaults.gear), + ); this.player.setConsumes(eventID, this.individualConfig.defaults.consumes); - this.player.setTalentsString(eventID, this.individualConfig.defaults.talents.talentsString); - this.player.setGlyphs(eventID, this.individualConfig.defaults.talents.glyphs || Glyphs.create()); + this.player.setTalentsString( + eventID, + this.individualConfig.defaults.talents.talentsString, + ); + this.player.setGlyphs( + eventID, + this.individualConfig.defaults.talents.glyphs || Glyphs.create(), + ); this.player.setSpecOptions(eventID, this.individualConfig.defaults.specOptions); this.player.setBuffs(eventID, this.individualConfig.defaults.individualBuffs); this.player.getParty()!.setBuffs(eventID, this.individualConfig.defaults.partyBuffs); this.player.getRaid()!.setBuffs(eventID, this.individualConfig.defaults.raidBuffs); this.player.setEpWeights(eventID, this.individualConfig.defaults.epWeights); - const defaultRatios = this.player.getDefaultEpRatios(tankSpec, healingSpec) + const defaultRatios = this.player.getDefaultEpRatios(tankSpec, healingSpec); this.player.setEpRatios(eventID, defaultRatios); - this.player.setProfession1(eventID, this.individualConfig.defaults.other?.profession1 || Profession.Engineering); - this.player.setProfession2(eventID, this.individualConfig.defaults.other?.profession2 || Profession.Jewelcrafting); - this.player.setDistanceFromTarget(eventID, this.individualConfig.defaults.other?.distanceFromTarget || 0); - this.player.setChannelClipDelay(eventID, this.individualConfig.defaults.other?.channelClipDelay || 0); - this.player.setNibelungAverageCasts(eventID, this.individualConfig.defaults.other?.nibelungAverageCasts || 11); + this.player.setProfession1( + eventID, + this.individualConfig.defaults.other?.profession1 || Profession.Engineering, + ); + this.player.setProfession2( + eventID, + this.individualConfig.defaults.other?.profession2 || Profession.Jewelcrafting, + ); + this.player.setDistanceFromTarget( + eventID, + this.individualConfig.defaults.other?.distanceFromTarget || 0, + ); + this.player.setChannelClipDelay( + eventID, + this.individualConfig.defaults.other?.channelClipDelay || 0, + ); + this.player.setNibelungAverageCasts( + eventID, + this.individualConfig.defaults.other?.nibelungAverageCasts || 11, + ); if (this.isWithinRaidSim) { this.sim.raid.setTargetDummies(eventID, 0); @@ -466,10 +575,8 @@ export abstract class IndividualSimUI extends SimUI { toProto(exportCategories?: Array): IndividualSimSettings { const exportCategory = (cat: SimSettingCategories) => - !exportCategories - || exportCategories.length == 0 - || exportCategories.includes(cat); - + !exportCategories || exportCategories.length == 0 || exportCategories.includes(cat); + const proto = IndividualSimSettings.create({ player: this.player.toProto(true, false, exportCategories), }); @@ -510,11 +617,13 @@ export abstract class IndividualSimUI extends SimUI { return Exporters.IndividualLinkExporter.createLink(this); } - fromProto(eventID: EventID, settings: IndividualSimSettings, includeCategories?: Array) { + fromProto( + eventID: EventID, + settings: IndividualSimSettings, + includeCategories?: Array, + ) { const loadCategory = (cat: SimSettingCategories) => - !includeCategories - || includeCategories.length == 0 - || includeCategories.includes(cat); + !includeCategories || includeCategories.length == 0 || includeCategories.includes(cat); const tankSpec = isTankSpec(this.player.spec); const healingSpec = isHealingSpec(this.player.spec); @@ -539,7 +648,10 @@ export abstract class IndividualSimUI extends SimUI { this.sim.raid.setTargetDummies(eventID, settings.targetDummies); } if (loadCategory(SimSettingCategories.Encounter)) { - this.sim.encounter.fromProto(eventID, settings.encounter || EncounterProto.create()); + this.sim.encounter.fromProto( + eventID, + settings.encounter || EncounterProto.create(), + ); } if (loadCategory(SimSettingCategories.UISettings)) { if (settings.epWeightsStats) { @@ -550,7 +662,9 @@ export abstract class IndividualSimUI extends SimUI { const defaultRatios = this.player.getDefaultEpRatios(tankSpec, healingSpec); if (settings.epRatios) { - const missingRatios = new Array(defaultRatios.length - settings.epRatios.length).fill(0); + const missingRatios = new Array( + defaultRatios.length - settings.epRatios.length, + ).fill(0); this.player.setEpRatios(eventID, settings.epRatios.concat(missingRatios)); } else { this.player.setEpRatios(eventID, defaultRatios); diff --git a/ui/core/sim_ui.ts b/ui/core/sim_ui.ts index 102d0b1252..ea229129c8 100644 --- a/ui/core/sim_ui.ts +++ b/ui/core/sim_ui.ts @@ -1,26 +1,24 @@ +import { BaseModal } from './components/base_modal.js'; import { Component } from './components/component.js'; import { NumberPicker } from './components/number_picker.js'; import { ResultsViewer } from './components/results_viewer.js'; -import { SimTitleDropdown } from './components/sim_title_dropdown.js'; import { SimHeader } from './components/sim_header'; +import { SimTab } from './components/sim_tab.js'; +import { SimTitleDropdown } from './components/sim_title_dropdown.js'; +import { SocialLinks } from './components/social_links.jsx'; +import { LaunchStatus } from './launched_sims.js'; import { Spec } from './proto/common.js'; import { ActionId } from './proto_utils/action_id.js'; -import { LaunchStatus } from './launched_sims.js'; - import { Sim, SimError } from './sim.js'; import { EventID, TypedEvent } from './typed_event.js'; -import { SimTab } from './components/sim_tab.js'; -import { BaseModal } from './components/base_modal.js'; - const URLMAXLEN = 2048; -const globalKnownIssues: Array = [ -]; +const globalKnownIssues: Array = []; // Config for displaying a warning to the user whenever a condition is met. export interface SimWarning { - updateOn: TypedEvent, - getContent: () => string | Array, + updateOn: TypedEvent; + getContent: () => string | Array; } export interface SimUIConfig { @@ -29,10 +27,10 @@ export interface SimUIConfig { // Scheme used for themeing on a per-class Basis or for other sims cssScheme: string; // The spec, if an individual sim, or null if the raid sim. - spec: Spec | null, - launchStatus: LaunchStatus, - knownIssues?: Array, - noticeText?: string, + spec: Spec | null; + launchStatus: LaunchStatus; + knownIssues?: Array; + noticeText?: string; } // Shared UI for all individual sims and the raid sim. @@ -45,7 +43,7 @@ export abstract class SimUI extends Component { // Emits when anything from the sim, raid, or encounter changes. readonly changeEmitter; - readonly resultsViewer: ResultsViewer + readonly resultsViewer: ResultsViewer; readonly simHeader: SimHeader; readonly simContentContainer: HTMLElement; @@ -60,17 +58,23 @@ export abstract class SimUI extends Component { this.cssClass = config.cssClass; this.cssScheme = config.cssScheme; this.isWithinRaidSim = this.rootElem.closest('.within-raid-sim') != null; + config.noticeText = `Cataclysm sim development has begun! Join our Discord for more details or to contribute to the project.`; this.rootElem.innerHTML = `
      - ${config.noticeText ? `
      ${config.noticeText}
      ` : ''} + ${ + config.noticeText + ? `
      ${config.noticeText}
      ` + : '' + }
      @@ -89,17 +93,14 @@ export abstract class SimUI extends Component { this.rootElem.classList.add('not-within-raid-sim'); } - this.changeEmitter = TypedEvent.onAny([ - this.sim.changeEmitter, - ], 'SimUIChange'); + this.changeEmitter = TypedEvent.onAny([this.sim.changeEmitter], 'SimUIChange'); this.sim.crashEmitter.on((eventID: EventID, error: SimError) => this.handleCrash(error)); const updateShowDamageMetrics = () => { if (this.sim.getShowDamageMetrics()) this.rootElem.classList.remove('hide-damage-metrics'); - else - this.rootElem.classList.add('hide-damage-metrics'); + else this.rootElem.classList.add('hide-damage-metrics'); }; updateShowDamageMetrics(); this.sim.showDamageMetricsChangeEmitter.on(updateShowDamageMetrics); @@ -107,8 +108,7 @@ export abstract class SimUI extends Component { const updateShowThreatMetrics = () => { if (this.sim.getShowThreatMetrics()) this.rootElem.classList.remove('hide-threat-metrics'); - else - this.rootElem.classList.add('hide-threat-metrics'); + else this.rootElem.classList.add('hide-threat-metrics'); }; updateShowThreatMetrics(); this.sim.showThreatMetricsChangeEmitter.on(updateShowThreatMetrics); @@ -116,8 +116,7 @@ export abstract class SimUI extends Component { const updateShowHealingMetrics = () => { if (this.sim.getShowHealingMetrics()) this.rootElem.classList.remove('hide-healing-metrics'); - else - this.rootElem.classList.add('hide-healing-metrics'); + else this.rootElem.classList.add('hide-healing-metrics'); }; updateShowHealingMetrics(); this.sim.showHealingMetricsChangeEmitter.on(updateShowHealingMetrics); @@ -142,39 +141,45 @@ export abstract class SimUI extends Component { this.sim.showThreatMetricsChangeEmitter.on(updateShowEpRatios); const updateShowExperimental = () => { - if (this.sim.getShowExperimental()) - this.rootElem.classList.remove('hide-experimental'); - else - this.rootElem.classList.add('hide-experimental'); + if (this.sim.getShowExperimental()) this.rootElem.classList.remove('hide-experimental'); + else this.rootElem.classList.add('hide-experimental'); }; updateShowExperimental(); this.sim.showExperimentalChangeEmitter.on(updateShowExperimental); this.addKnownIssues(config); + // Sidebar Contents + const titleElem = this.rootElem.querySelector('.sim-title') as HTMLElement; new SimTitleDropdown(titleElem, config.spec, { noDropdown: this.isWithinRaidSim }); - const resultsViewerElem = this.rootElem.getElementsByClassName('sim-sidebar-results')[0] as HTMLElement; - this.resultsViewer = new ResultsViewer(resultsViewerElem); - - this.simActionsContainer = this.rootElem.getElementsByClassName('sim-sidebar-actions')[0] as HTMLElement; - - new NumberPicker(this.simActionsContainer, this.sim, { + this.simActionsContainer = this.rootElem.querySelector( + '.sim-sidebar-actions', + ) as HTMLElement; + this.iterationsPicker = new NumberPicker(this.simActionsContainer, this.sim, { label: 'Iterations', - extraCssClasses: [ - 'iterations-picker', - 'within-raid-sim-hide', - ], + extraCssClasses: ['iterations-picker', 'within-raid-sim-hide'], changedEvent: (sim: Sim) => sim.iterationsChangeEmitter, getValue: (sim: Sim) => sim.getIterations(), setValue: (eventID: EventID, sim: Sim, newValue: number) => { sim.setIterations(eventID, newValue); }, - }); + }).rootElem; + + const resultsViewerElem = this.rootElem.querySelector( + '.sim-sidebar-results', + ) as HTMLElement; + this.resultsViewer = new ResultsViewer(resultsViewerElem); + + const socialsContainer = this.rootElem.querySelector('.sim-sidebar-socials') as HTMLElement; + socialsContainer.appendChild(SocialLinks.buildDiscordLink()); + socialsContainer.appendChild(SocialLinks.buildGitHubLink()); + socialsContainer.appendChild(SocialLinks.buildPatreonLink()); - this.iterationsPicker = this.rootElem.getElementsByClassName('iterations-picker')[0] as HTMLElement; - this.simTabContentsContainer = this.rootElem.querySelector('.sim-main.tab-content') as HTMLElement; + this.simTabContentsContainer = this.rootElem.querySelector( + '.sim-main.tab-content', + ) as HTMLElement; if (!this.isWithinRaidSim) { window.addEventListener('message', async event => { @@ -220,11 +225,14 @@ export abstract class SimUI extends Component { private addKnownIssues(config: SimUIConfig) { let statusStr = ''; if (config.launchStatus == LaunchStatus.Unlaunched) { - statusStr = 'This sim is a WORK IN PROGRESS. It is not fully developed and should not be used for general purposes.'; + statusStr = + 'This sim is a WORK IN PROGRESS. It is not fully developed and should not be used for general purposes.'; } else if (config.launchStatus == LaunchStatus.Alpha) { - statusStr = 'This sim is in ALPHA. Bugs are expected; please let us know if you find one!'; + statusStr = + 'This sim is in ALPHA. Bugs are expected; please let us know if you find one!'; } else if (config.launchStatus == LaunchStatus.Beta) { - statusStr = 'This sim is in BETA. There may still be a few bugs; please let us know if you find one!'; + statusStr = + 'This sim is in BETA. There may still be a few bugs; please let us know if you find one!'; } if (statusStr) { config.knownIssues = [statusStr].concat(config.knownIssues || []); @@ -252,7 +260,7 @@ export abstract class SimUI extends Component { return this.rootElem.classList.contains('individual-sim-ui'); } - async runSim(onProgress: Function) { + async runSim(onProgress: (_?: any) => void) { this.resultsViewer.setPending(); try { await this.sim.runRaidSim(TypedEvent.nextEventID(), onProgress); @@ -286,48 +294,60 @@ export abstract class SimUI extends Component { return; } - if (window.confirm('Simulation Failure:\n' + errorStr + '\nPress Ok to file crash report')) { + if ( + window.confirm('Simulation Failure:\n' + errorStr + '\nPress Ok to file crash report') + ) { // Splice out just the line numbers const hash = this.hashCode(errorStr); const link = this.toLink(); const rngSeed = this.sim.getLastUsedRngSeed(); - fetch('https://api.github.com/search/issues?q=is:issue+is:open+repo:wowsims/wotlk+' + hash).then(resp => { - resp.json().then((issues) => { - if (issues.total_count > 0) { - window.open(issues.items[0].html_url, '_blank'); - } else { - const base_url = 'https://github.com/wowsims/wotlk/issues/new?assignees=&labels=&title=Crash%20Report%20' - const base = `${base_url}${hash}&body=`; - const maxBodyLength = URLMAXLEN - base.length; - let issueBody = encodeURIComponent(`Link:\n${link}\n\nRNG Seed: ${rngSeed}\n\n${errorStr}`); - if (link.includes('/raid/')) { - // Move the actual error before the link, as it will likely get truncated. - issueBody = encodeURIComponent(`${errorStr}\nRNG Seed: ${rngSeed}\nLink:\n${link}`); - } - let truncated = false; - while (issueBody.length > maxBodyLength - (truncated ? 3 : 0)) { - issueBody = issueBody.slice(0, issueBody.lastIndexOf('%')) // Avoid truncating in the middle of a URLencoded segment. - truncated = true; + fetch( + 'https://api.github.com/search/issues?q=is:issue+is:open+repo:wowsims/wotlk+' + + hash, + ) + .then(resp => { + resp.json().then(issues => { + if (issues.total_count > 0) { + window.open(issues.items[0].html_url, '_blank'); + } else { + const base_url = + 'https://github.com/wowsims/wotlk/issues/new?assignees=&labels=&title=Crash%20Report%20'; + const base = `${base_url}${hash}&body=`; + const maxBodyLength = URLMAXLEN - base.length; + let issueBody = encodeURIComponent( + `Link:\n${link}\n\nRNG Seed: ${rngSeed}\n\n${errorStr}`, + ); + if (link.includes('/raid/')) { + // Move the actual error before the link, as it will likely get truncated. + issueBody = encodeURIComponent( + `${errorStr}\nRNG Seed: ${rngSeed}\nLink:\n${link}`, + ); + } + let truncated = false; + while (issueBody.length > maxBodyLength - (truncated ? 3 : 0)) { + issueBody = issueBody.slice(0, issueBody.lastIndexOf('%')); // Avoid truncating in the middle of a URLencoded segment. + truncated = true; + } + if (truncated) { + issueBody += '...'; + // The raid links are too large and will always cause truncation. + // Prompt the user to add more information to the issue. + new CrashModal(this.rootElem, link); + } + window.open(base + issueBody, '_blank'); } - if (truncated) { - issueBody += "..."; - // The raid links are too large and will always cause truncation. - // Prompt the user to add more information to the issue. - new CrashModal(this.rootElem, link); - } - window.open(base + issueBody, '_blank'); - } + }); + }) + .catch(fetchErr => { + alert('Failed to file report... try again another time:' + fetchErr); }); - }).catch(fetchErr => { - alert('Failed to file report... try again another time:' + fetchErr); - }); } } hashCode(str: string): number { let hash = 0; for (let i = 0, len = str.length; i < len; i++) { - let chr = str.charCodeAt(i); + const chr = str.charCodeAt(i); hash = (hash << 5) - hash + chr; hash |= 0; // Convert to 32bit integer } @@ -347,7 +367,7 @@ class CrashModal extends BaseModal {
      `; - let text = document.createTextNode(link); + const text = document.createTextNode(link); this.body.querySelector('textarea')?.appendChild(text); } } diff --git a/ui/scss/core/components/individual_sim_ui/_gem_summary.scss b/ui/scss/core/components/individual_sim_ui/_gem_summary.scss index e6821dcfd2..32be2e1067 100644 --- a/ui/scss/core/components/individual_sim_ui/_gem_summary.scss +++ b/ui/scss/core/components/individual_sim_ui/_gem_summary.scss @@ -1,33 +1,34 @@ .gem-summary-root { - width: 50%; + width: 50%; + margin-top: map-get($spacers, 5); - .gem-summary-link { - --gem-width: 2rem; + .gem-summary-link { + --gem-width: 2rem; - display: flex; - align-items: center; + display: flex; + align-items: center; - .gem-icon { - position: unset; - margin-right: map-get($spacers, 1); - border-radius: 0; - border: $border-default; - } + .gem-icon { + position: unset; + margin-right: map-get($spacers, 1); + border-radius: 0; + border: $border-default; + } - &:not(:last-child) { - margin-bottom: map-get($spacers, 1); - } - } + &:not(:last-child) { + margin-bottom: map-get($spacers, 1); + } + } } @include media-breakpoint-down(xl) { - .gem-summary-root { - margin-bottom: var(--container-padding); - } + .gem-summary-root { + margin-bottom: var(--container-padding); + } } @include media-breakpoint-down(md) { - .gem-summary-root { - width: 100%; - } -} \ No newline at end of file + .gem-summary-root { + width: 100%; + } +} diff --git a/ui/scss/core/sim_ui/_sidebar.scss b/ui/scss/core/sim_ui/_sidebar.scss index 6e81855235..51bce579e1 100644 --- a/ui/scss/core/sim_ui/_sidebar.scss +++ b/ui/scss/core/sim_ui/_sidebar.scss @@ -1,98 +1,90 @@ -@use "sass:map"; +@use 'sass:map'; -@import "../components/sim_title_dropdown"; +@import '../components/sim_title_dropdown'; .sim-sidebar { - flex: 1; - display: flex; - flex-direction: column; - align-items: stretch; - background: $body-bg; - // This must be larger than the z-index on .sim-content otherwise the dropdown is overlapped - z-index: $sidebar-z-index; + flex: 1; + display: flex; + flex-direction: column; + align-items: stretch; + background: $body-bg; + // This must be larger than the z-index on .sim-content otherwise the dropdown is overlapped + z-index: $sidebar-z-index; - .sim-sidebar-content { - padding-top: $gap-width; - padding-left: $gap-width; - padding-right: $gap-width; - padding-bottom: map-get($spacers, 3); - display: flex; - flex-direction: column; + .sim-sidebar-content { + min-height: calc(100vh - $sim-header-height); + padding: $gap-width; + display: flex; + flex-direction: column; - &> *:not(:last-child) { - margin-bottom: map-get($spacers, 4); - } + & > *:not(:last-child) { + margin-bottom: map.get($spacers, 4); + } - .sim-sidebar-actions { - padding-left: var(--container-padding); - padding-right: var(--container-padding); - margin-left: $gap-width * -1; - margin-right: $gap-width * -1; - display: flex; - flex-direction: column; - align-items: center; + .sim-sidebar-actions { + padding-left: var(--container-padding); + padding-right: var(--container-padding); + margin-left: $gap-width * -1; + margin-right: $gap-width * -1; + display: flex; + flex-direction: column; + align-items: center; - &> *:not(:last-child) { - margin-bottom: $block-spacer; - } + & > *:not(:last-child) { + margin-bottom: $block-spacer; + } - .iterations-picker { - width: 100%; - flex-flow: column; + .iterations-picker { + width: 100%; + flex-flow: column; - .number-picker-input { - width: 100%; - margin: 0; - } - } - } + .number-picker-input { + width: 100%; + margin: 0; + } + } + } - .sim-sidebar-results { - min-height: 25vh; - display: flex; - justify-content: center; - align-items: center; - } - } -} + .sim-sidebar-results { + min-height: 25vh; + display: flex; + justify-content: center; + align-items: center; + } -@include media-breakpoint-down(xxl) { - .sim-sidebar { - .sim-sidebar-content { - .sim-sidebar-actions { - padding: 0; - margin: 0; - } - } - } + .sim-sidebar-stats { + margin-top: auto; + } + + .sim-sidebar-socials { + display: flex; + justify-content: center; + + & > *:not(:last-child) { + margin-right: map-get($spacers, 3); + } + } + } } -@include media-breakpoint-up(md) { - .sim-sidebar { - // Allow the stats to align to the bottom of the viewport if the section is small enough to avoid scrolling - // 'Smaller' is set when banner is displayed - .sim-sidebar-content.smaller { - min-height: calc(100vh - calc($sim-header-height + 3.5rem)); - } - .sim-sidebar-content { - min-height: calc(100vh - calc($sim-header-height + 0.2rem)); +@include media-breakpoint-down(xxl) { + .sim-sidebar { + .sim-sidebar-content { + padding-left: $gap-width-sm * 2; + padding-right: $gap-width-sm * 2; - .sim-sidebar-footer { - flex: 1; - display: flex; - align-items: flex-end; - } - } - } + .sim-sidebar-actions { + padding: 0; + margin: 0; + } + } + } } @include media-breakpoint-down(lg) { - .sim-sidebar { - .sim-sidebar-content { - padding-top: $gap-width-sm * 2; - padding-bottom: $gap-width-sm; - padding-left: $gap-width-sm; - padding-right: $gap-width-sm; - } - } + .sim-sidebar { + .sim-sidebar-content { + padding: $gap-width-sm * 2 $gap-width-sm; + } + } } diff --git a/ui/scss/shared/_variables.scss b/ui/scss/shared/_variables.scss index 934d9e272b..a3f73b1a6b 100644 --- a/ui/scss/shared/_variables.scss +++ b/ui/scss/shared/_variables.scss @@ -1,4 +1,4 @@ -@use "sass:map"; +@use 'sass:map'; // Define and overwrite Bootstrap Variables // See bootstrap/scss/_variables.scss for a complete list of definitions, or @@ -13,41 +13,41 @@ $brand: #e0a335; $success: #1eff00; $death-knight: rgb(194, 46, 70); -$druid: rgb(255, 125, 10); -$hunter: rgb(171, 212, 115); -$mage: rgb(105, 204, 240); -$paladin: rgb(245, 140, 186); -$priest: rgb(255, 255, 255); -$rogue: rgb(255, 245, 105); -$shaman: rgb(36, 89, 255); -$warlock: rgb(148, 130, 201); -$warrior: rgb(199, 156, 110); +$druid: rgb(255, 125, 10); +$hunter: rgb(171, 212, 115); +$mage: rgb(105, 204, 240); +$paladin: rgb(245, 140, 186); +$priest: rgb(255, 255, 255); +$rogue: rgb(255, 245, 105); +$shaman: rgb(36, 89, 255); +$warlock: rgb(148, 130, 201); +$warrior: rgb(199, 156, 110); $class-colors: ( - "death-knight": $death-knight, - "druid": $druid, - "hunter": $hunter, - "mage": $mage, - "paladin": $paladin, - "priest": $priest, - "raid": $brand, - "rogue": $rogue, - "shaman": $shaman, - "warlock": $warlock, - "warrior": $warrior, + 'death-knight': $death-knight, + 'druid': $druid, + 'hunter': $hunter, + 'mage': $mage, + 'paladin': $paladin, + 'priest': $priest, + 'raid': $brand, + 'rogue': $rogue, + 'shaman': $shaman, + 'warlock': $warlock, + 'warrior': $warrior, ); $custom-colors: ( - "brand": $brand, - "primary": $primary, - "success": $success, + 'brand': $brand, + 'primary': $primary, + 'success': $success, ); $custom-breakpoints: ( - xxxl: 1600px, - 1080p: 1900px, - 1440p: 2500px, - 4k: 3800px, + xxxl: 1600px, + 1080p: 1900px, + 1440p: 2500px, + 4k: 3800px, ); // Borders @@ -55,56 +55,56 @@ $border-color: dimgrey; $border-default: 1px solid $border-color; // Colors -$item-quality-junk: #9d9d9d; -$item-quality-common: #ffffff; -$item-quality-uncommon: #1eff00; -$item-quality-rare: #0070dd; -$item-quality-epic: #a335ee; +$item-quality-junk: #9d9d9d; +$item-quality-common: #ffffff; +$item-quality-uncommon: #1eff00; +$item-quality-rare: #0070dd; +$item-quality-epic: #a335ee; $item-quality-legendary: #ff8000; -$item-quality-artifact: #e5cc80; -$item-quality-heirloom: #0cf; +$item-quality-artifact: #e5cc80; +$item-quality-heirloom: #0cf; $item-qualities: ( - junk: $item-quality-junk, - common: $item-quality-common, - uncommon: $item-quality-uncommon, - rare: $item-quality-rare, - epic: $item-quality-epic, - legendary: $item-quality-legendary, - artifact: $item-quality-artifact, - heirloom: $item-quality-heirloom + junk: $item-quality-junk, + common: $item-quality-common, + uncommon: $item-quality-uncommon, + rare: $item-quality-rare, + epic: $item-quality-epic, + legendary: $item-quality-legendary, + artifact: $item-quality-artifact, + heirloom: $item-quality-heirloom, ); -$health-color: #22ba00; -$mana-color: #2E93fA; -$energy-color: #ffd700; -$rage-color: #ff0000; -$focus-color: #cd853f; -$combo-points-color:#ffa07a; +$health-color: #22ba00; +$mana-color: #2e93fa; +$energy-color: #ffd700; +$rage-color: #ff0000; +$focus-color: #cd853f; +$combo-points-color: #ffa07a; $resource-colors: ( - combo-points: $combo-points-color, - energy: $energy-color, - focus: $focus-color, - health: $health-color, - mana: $mana-color, - rage: $rage-color, + combo-points: $combo-points-color, + energy: $energy-color, + focus: $focus-color, + health: $health-color, + mana: $mana-color, + rage: $rage-color, ); -$link-danger-color: #ef9eaa; +$link-danger-color: #ef9eaa; $link-warning-color: #faf07f; $table-row-even-bg: #202128; $table-row-odd-bg: #2d2f35; -$wrath-blue: #7FCBD8; +$wrath-blue: #7fcbd8; // Spacers -$block-spacer: .75rem; -$table-cell-padding: .5rem; -$content-font-size: .75rem; +$block-spacer: 0.75rem; +$table-cell-padding: 0.5rem; +$content-font-size: 0.75rem; $gap-width: 1.5rem; -$gap-width-sm: .5rem; +$gap-width-sm: 0.5rem; // Sizing $icon-size-sm: 1rem; @@ -126,7 +126,7 @@ $grid-breakpoints: map-merge($grid-breakpoints, $custom-breakpoints); $body-bg: #15171e; $body-color: white; $font-size-root: 16px; -$grid-gutter-width: .75rem; +$grid-gutter-width: 0.75rem; // This option doesn't seem to be working for some reason. Carets hidden manually instead // $enable-caret: false; @@ -136,12 +136,12 @@ $enable-rounded: false; $link-color: #a5b1d6; $link-decoration: none; $link-hover-color: white; -$link-transition: color .15s ease-in-out; +$link-transition: color 0.15s ease-in-out; // Component variable overrides $btn-hover-bg-shade-amount: 30%; -$btn-font-size: .875rem; -$btn-padding-y: .5rem; +$btn-font-size: 0.875rem; +$btn-padding-y: 0.5rem; $dropdown-bg: $body-bg; $dropdown-border-width: 0; @@ -156,12 +156,12 @@ $form-check-input-bg: $link-color; $form-check-input-border: 1px solid lighten($body-bg, 20); $form-check-input-width: 2rem; $form-check-input-focus-border: lighten($body-bg, 20); -$form-check-input-focus-box-shadow: 0 0 .25rem $primary; +$form-check-input-focus-box-shadow: 0 0 0.25rem $primary; $form-check-input-checked-bg-color: $primary; $form-check-input-checked-bg-image: url("data:image/svg+xml,"); $form-check-input-checked-border-color: lighten($body-bg, 20); -$form-label-margin-bottom: .25rem; +$form-label-margin-bottom: 0.25rem; $form-label-font-size: $content-font-size; $form-label-font-weight: normal; @@ -170,7 +170,7 @@ $form-select-border-color: lighten($body-bg, 20); $form-select-color: white; $form-select-disabled-bg: lighten($body-bg, 20); $form-select-focus-border-color: lighten($body-bg, 20); -$form-select-focus-box-shadow: 0 0 .25rem lighten($body-bg, 20); +$form-select-focus-box-shadow: 0 0 0.25rem lighten($body-bg, 20); $form-select-font-size: $content-font-size; $form-select-indicator: url("data:image/svg+xml,"); @@ -180,7 +180,7 @@ $input-color: white; $input-disabled-bg: lighten($body-bg, 20); $input-focus-bg: lighten($body-bg, 12); $input-focus-border-color: lighten($body-bg, 20); -$input-focus-box-shadow: 0 0 .25rem lighten($body-bg, 20); +$input-focus-box-shadow: 0 0 0.25rem lighten($body-bg, 20); $input-focus-color: white; $input-font-size: $content-font-size; @@ -196,7 +196,7 @@ $navbar-dark-hover-color: $primary; $navbar-dark-active-color: $primary; $navbar-padding-y: 0; -$nav-link-font-size: .875rem; +$nav-link-font-size: 0.875rem; $nav-link-padding-y: map-get($spacers, 3); $nav-link-transition: $link-transition; @@ -210,6 +210,7 @@ $popover-body-padding-x: map-get($spacers, 2); $popover-body-padding-y: map-get($spacers, 2); $tooltip-bg: $popover-bg; +$tooltip-color: white; $tooltip-max-width: 15vw; $tooltip-max-width-lg: 25vw; $tooltip-max-width-sm: 75vw;