// Copyright (c) 2023, Compiler Explorer Authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. import $ from 'jquery'; import {options} from '../options.js'; import {CompilerInfo} from '../compiler.interfaces.js'; import {assert} from '../assert.js'; import {localStorage} from '../local.js'; import { ConfiguredRuntimeTool, ConfiguredRuntimeTools, PossibleRuntimeTools, RuntimeToolOption, RuntimeToolOptions, RuntimeToolType, } from '../../types/execution/execution.interfaces.js'; const FAV_RUNTIMETOOLS_STORE_KEY = 'favruntimetools'; export type RuntimeToolsChangeCallback = () => void; type FavRuntimeTool = { name: RuntimeToolType; options: string; meta: string; }; type FavRuntimeTools = FavRuntimeTool[]; export class RuntimeToolsWidget { private domRoot: JQuery; private popupDomRoot: JQuery; private envVarsInput: JQuery; private dropdownButton: JQuery; private onChangeCallback: RuntimeToolsChangeCallback; private configured: ConfiguredRuntimeTools = []; private compiler: CompilerInfo | undefined; private possibleTools: PossibleRuntimeTools; constructor(domRoot: JQuery, dropdownButton: JQuery, onChangeCallback: RuntimeToolsChangeCallback) { this.domRoot = domRoot; this.popupDomRoot = $('#runtimetools-selection'); this.dropdownButton = dropdownButton; this.envVarsInput = this.popupDomRoot.find('.envvars'); this.onChangeCallback = onChangeCallback; this.possibleTools = []; } private loadStateFromUI(): ConfiguredRuntimeTools { const tools: ConfiguredRuntimeTools = []; const envOverrides = this.getEnvOverrides(); if (envOverrides.length > 0) { tools.push({ name: RuntimeToolType.env, options: envOverrides, }); } const selects = this.popupDomRoot.find('select'); for (const select of selects) { const jqSelect = $(select); const rawName = jqSelect.data('tool-name'); const optionName = jqSelect.data('tool-option'); const val = jqSelect.val(); if (val) { const name = rawName as RuntimeToolType; assert(name !== RuntimeToolType.env); let tool = tools.find(tool => tool.name === name); if (!tool) { tool = { name: name, options: [], }; tools.push(tool); } const option: RuntimeToolOption = { name: optionName, value: (val || '') as string, }; tool.options.push(option); } } return tools; } private optionsToString(options: RuntimeToolOptions): string { return options.map(env => `${env.name}=${env.value}`).join('\n'); } private stringToOptions(options: string): RuntimeToolOptions { return options .split('\n') .map(env => { const firstEqPos = env.indexOf('='); if (firstEqPos !== -1) { return { name: env.substring(0, firstEqPos), value: env.substring(firstEqPos + 1), }; } else { return false; } }) .filter(Boolean) as RuntimeToolOptions; } private getEnvOverrides(): RuntimeToolOptions { return this.stringToOptions(this.envVarsInput.val() as string); } private selectOverrideFromFave(event) { const elem = $(event.target).parent(); const name = elem.data('ov-name'); const optionsStr = elem.data('ov-options'); const options = this.stringToOptions(optionsStr); const tool = this.possibleTools.find(ov => ov.name === name); if (tool) { const configuredTools = this.loadStateFromUI(); let configuredTool = configuredTools.find(t => t.name === name); if (!configuredTool) { configuredTool = { name: name, options: [], }; configuredTools.push(configuredTool); } configuredTool.options = options; this.loadStateIntoUI(configuredTools); } } private newFavoriteOverrideDiv(fave: FavRuntimeTool) { const div = $('#overrides-favorite-tpl').children().clone(); const prefix = fave.name + ': '; div.find('.overrides-name').html(prefix + fave.options.replace(/\n/g, ', ')); div.data('ov-name', fave.name); div.data('ov-options', fave.options); div.on('click', this.selectOverrideFromFave.bind(this)); return div; } private loadFavoritesIntoUI() { const favoritesDiv = this.popupDomRoot.find('.runtimetools-favorites'); favoritesDiv.html(''); const faves = this.getFavorites(); for (const fave of faves) { const div: any = this.newFavoriteOverrideDiv(fave); favoritesDiv.append(div); } } private addToFavorites(override: ConfiguredRuntimeTool) { if (override.name === RuntimeToolType.env) return; const faves = this.getFavorites(); const fave: FavRuntimeTool = { name: override.name, options: this.optionsToString(override.options), meta: this.compiler?.baseName || this.compiler?.groupName || this.compiler?.name || this.compiler?.id || '', }; faves.push(fave); this.setFavorites(faves); } private removeFromFavorites(override: ConfiguredRuntimeTool) { if (override.name === RuntimeToolType.env) return; const overrideOptions = this.optionsToString(override.options); const faves = this.getFavorites(); const faveIdx = faves.findIndex(f => f.name === override.name && f.options === overrideOptions); if (faveIdx !== -1) { faves.splice(faveIdx, 1); this.setFavorites(faves); } } private isAFavorite(override: ConfiguredRuntimeTool) { if (override.name === RuntimeToolType.env) return false; const overrideOptions = this.optionsToString(override.options); const faves = this.getFavorites(); const fave = faves.find(f => f.name === override.name && f.options === overrideOptions); return !!fave; } private cap(text: string) { if (text.length > 0) { return text[0].toUpperCase() + text.substring(1); } return ''; } private loadStateIntoUI(configured: ConfiguredRuntimeTools) { this.envVarsInput.val(''); for (const config of configured) { if (config.name === RuntimeToolType.env) { this.envVarsInput.val(this.optionsToString(config.options)); } } const container = this.popupDomRoot.find('.possible-runtimetools'); container.html(''); this.possibleTools = this.compiler?.possibleRuntimeTools || []; for (const possibleTool of this.possibleTools) { const card = $('#possible-runtime-tool').children().clone(); card.find('.tool-name').html(this.cap(possibleTool.name)); card.find('.tool-description').html(possibleTool.description); const toolOptionsDiv = card.find('.tool-options'); const faveButton = card.find('.tool-fav-button'); faveButton.hide(); const faveStar = faveButton.find('.tool-fav-btn-icon'); const config = configured.find(c => c.name === possibleTool.name); for (const toolOption of possibleTool.possibleOptions) { const optionDiv = $('#possible-runtime-tool-option').children().clone(); optionDiv.attr('name', toolOption.name); const display_text = this.cap(toolOption.name); optionDiv.find('.tool-option-name').html(display_text); const select = optionDiv.find('select'); select.data('tool-name', possibleTool.name); select.data('tool-option', toolOption.name); const option = $('