diff --git a/assets/locales/en/translation.json b/assets/locales/en/translation.json index a6a250ec95..9f35ed6e80 100644 --- a/assets/locales/en/translation.json +++ b/assets/locales/en/translation.json @@ -26,6 +26,7 @@ "name": "Name", "search": "Search", "filter": "Filter", + "elapsed_time": "Elapsed Time", "phases": { "1": "Phase 1 (5.0 - T14)", "2": "Phase 2 (5.1)", @@ -2331,6 +2332,7 @@ "breakpoints_implemented": "The following breakpoints have been implemented for this spec:", "post_cap_ep": "Post cap EP", "reforge_optimization_failed": "Reforge optimization failed. Please try again, or report the issue if it persists.", + "reforge_optimization_cancelled": "Reforge optimization cancelled.", "use_custom": "Use custom EP Weights", "enable_modification": "This will enable modification of the default EP weights and setting custom stat caps.", "modify_in_editor": "Ep weights can be modified in the Stat Weights editor.", diff --git a/assets/locales/fr/translation.json b/assets/locales/fr/translation.json index 7e637188f8..626fe581bc 100644 --- a/assets/locales/fr/translation.json +++ b/assets/locales/fr/translation.json @@ -26,6 +26,7 @@ "name": "Nom", "search": "Rechercher", "filter": "Filtrer", + "elapsed_time": "Temps écoulé", "phases": { "1": "Phase 1 (5.0 - T14)", "2": "Phase 2 (5.1)", @@ -2331,6 +2332,7 @@ "breakpoints_implemented": "Les breakpoints suivants ont été implémentés pour cette spé :", "post_cap_ep": "PE post-cap", "reforge_optimization_failed": "L'optimisation de la retouche a échoué. Veuillez réessayer ou signaler le problème s'il persiste.", + "reforge_optimization_cancelled": "Optimisation de la retouche annulée.", "use_custom": "Utiliser des poids PE personnalisés", "enable_modification": "Ceci permettra la modification des poids PE par défaut et la définition de caps de statistiques personnalisés.", "modify_in_editor": "Les poids PE peuvent être modifiés dans l'éditeur de Poids des Statistiques.", diff --git a/schemas/translation.schema.json b/schemas/translation.schema.json index 04e08421dd..9425797966 100644 --- a/schemas/translation.schema.json +++ b/schemas/translation.schema.json @@ -107,6 +107,9 @@ "filter": { "type": "string" }, + "elapsed_time": { + "type": "string" + }, "phases": { "type": "object", "properties": { @@ -9748,6 +9751,9 @@ "reforge_optimization_failed": { "type": "string" }, + "reforge_optimization_cancelled": { + "type": "string" + }, "use_custom": { "type": "string" }, diff --git a/ui/core/components/base_modal.tsx b/ui/core/components/base_modal.tsx index 4fa66ae21c..6b82028c1b 100644 --- a/ui/core/components/base_modal.tsx +++ b/ui/core/components/base_modal.tsx @@ -24,6 +24,8 @@ type BaseModalConfig = { title?: string | null; // Should the modal be disposed on close? disposeOnClose?: boolean; + // User should not be able to close the modal + preventClose?: boolean; }; const DEFAULT_CONFIG = { @@ -46,7 +48,7 @@ export class BaseModal extends Component { readonly body: HTMLElement; readonly footer: HTMLElement | undefined; - constructor(parent: HTMLElement, cssClass: string, config: BaseModalConfig = { disposeOnClose: true }) { + constructor(parent: HTMLElement, cssClass: string, config: BaseModalConfig = { disposeOnClose: true, preventClose: false }) { super(parent, 'modal'); this.modalConfig = { ...DEFAULT_CONFIG, ...config }; @@ -58,18 +60,22 @@ export class BaseModal extends Component { const modalSizeKlass = this.modalConfig.size && this.modalConfig.size != 'md' ? `modal-${this.modalConfig.size}` : ''; this.rootElem.classList.add('fade'); + if (this.modalConfig.preventClose) this.rootElem.classList.add('modal-static'); + this.rootElem.appendChild(
+ Reforging can be a lengthy process, especially as specific stat caps and breakpoints come into play for classes. This may take a while, + but be assured that the calculation will eventually complete. +
+You may cancel this operation at any time using the button below.
+ > + ), + onCancel: () => { + this.isCancelling = true; + if (isDevMode()) { + console.log('User cancelled reforge optimization'); + } + try { + this.pendingWorker?.terminate(); + } catch {} + if (this.previousGear) this.player.setGear(TypedEvent.nextEventID(), this.previousGear); + this.progressTrackerModal.hide(); + trackEvent({ + action: 'settings', + category: 'reforging', + label: 'suggest_cancel', + }); + + new Toast({ + variant: 'warning', + body: i18n.t('sidebar.buttons.suggest_reforges.reforge_optimization_cancelled'), + delay: 3000, + }); + }, + }); // Pre-warm the worker pool getReforgeWorkerPool().warmUp(); @@ -290,50 +330,27 @@ export class ReforgeOptimizer { const startReforgeOptimizationEntry: ActionGroupItem = { label: i18n.t('sidebar.buttons.suggest_reforges.title'), cssClass: 'suggest-reforges-action-button flex-grow-1', - onClick: async ({ currentTarget }) => { + onClick: async () => { + this.progressTrackerModal.show(); trackEvent({ action: 'settings', category: 'reforging', label: 'suggest_start', }); - const button = currentTarget as HTMLButtonElement; - if (button) { - button.classList.add('loading'); - button.disabled = true; - } - const wasCM = simUI.player.getChallengeModeEnabled(); + this.wasCM = simUI.player.getChallengeModeEnabled(); try { performance.mark('reforge-optimization-start'); - if (wasCM) { + if (this.wasCM) { simUI.player.setChallengeModeEnabled(TypedEvent.nextEventID(), false); } await this.optimizeReforges(); this.onReforgeDone(); } catch (error) { + if (this.isCancelling) return; this.onReforgeError(error); } finally { - if (wasCM) { - simUI.player.setChallengeModeEnabled(TypedEvent.nextEventID(), true); - } - performance.mark('reforge-optimization-end'); - const completionTimeInMs = performance.measure( - 'reforge-optimization-measure', - 'reforge-optimization-start', - 'reforge-optimization-end', - ).duration; - if (isDevMode()) console.log('Reforge optimization took:', `${completionTimeInMs.toFixed(2)}ms`); - - trackEvent({ - action: 'settings', - category: 'reforging', - label: 'suggest_duration', - value: Math.ceil(completionTimeInMs / 1000), - }); - if (button) { - button.classList.remove('loading'); - button.disabled = false; - } + this.onReforgeFinally(); } }, }; @@ -1720,8 +1737,8 @@ export class ReforgeOptimizer { const startTimeMs: number = Date.now(); - const workerPool = getReforgeWorkerPool(); - const solution: LPSolution = await workerPool.solve(model, { + this.pendingWorker = getReforgeWorkerPool(); + const solution: LPSolution = await this.pendingWorker.solve(model, { timeout: maxSeconds * 1000, tolerance: 0.005, // unused currently }); @@ -2101,6 +2118,7 @@ export class ReforgeOptimizer { label: 'suggest_error', value: error, }); + new Toast({ variant: 'error', body: ( @@ -2116,6 +2134,24 @@ export class ReforgeOptimizer { }); } + onReforgeFinally() { + this.progressTrackerModal.hide(); + + if (this.wasCM) { + this.simUI.player.setChallengeModeEnabled(TypedEvent.nextEventID(), true); + } + performance.mark('reforge-optimization-end'); + const completionTimeInMs = performance.measure('reforge-optimization-measure', 'reforge-optimization-start', 'reforge-optimization-end').duration; + if (isDevMode()) console.log('Reforge optimization took:', `${completionTimeInMs.toFixed(2)}ms`); + + trackEvent({ + action: 'settings', + category: 'reforging', + label: 'suggest_duration', + value: Math.ceil(completionTimeInMs / 1000), + }); + } + fromProto(eventID: EventID, proto: ReforgeSettings) { TypedEvent.freezeAllAndDo(() => { this.setUseCustomEPValues(eventID, proto.useCustomEpValues); diff --git a/ui/core/reforge_worker_pool.ts b/ui/core/reforge_worker_pool.ts index a7fb58fd8f..6ea4680351 100644 --- a/ui/core/reforge_worker_pool.ts +++ b/ui/core/reforge_worker_pool.ts @@ -242,6 +242,7 @@ export class ReforgeWorkerPool { terminate() { this.worker?.terminate(); this.worker = null; + ReforgeWorkerPool.instance = null; } } diff --git a/ui/scss/core/components/_progress_tracker_modal.scss b/ui/scss/core/components/_progress_tracker_modal.scss new file mode 100644 index 0000000000..0866fee9e9 --- /dev/null +++ b/ui/scss/core/components/_progress_tracker_modal.scss @@ -0,0 +1,26 @@ +.progress-tracker-modal { + margin: 0; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) !important; + + .modal-title { + margin-left: auto; + margin-right: auto; + text-align: center; + } +} + +.progress-tracker-modal-warning { + border: 1px solid var(--bs-warning); + padding: var(--spacer-3); + font-size: var(--btn-font-size); +} + +.progress-tracker-modal-content { + display: flex; + flex-direction: column; + gap: var(--spacer-3); + align-items: center; + text-align: center; +} diff --git a/ui/scss/core/individual_sim_ui/index.scss b/ui/scss/core/individual_sim_ui/index.scss index 22935d3164..525502e42c 100644 --- a/ui/scss/core/individual_sim_ui/index.scss +++ b/ui/scss/core/individual_sim_ui/index.scss @@ -33,6 +33,7 @@ @import '../components/tooltip_button'; @import '../components/unit_picker'; @import '../components/suggest_reforges_action'; +@import '../components/progress_tracker_modal'; @import '../components/individual_sim_ui';