mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 07:04:04 -05:00
## Summary Moves `static/assert.ts` and `static/rison.ts` to `shared/` directory to make them available to both frontend and backend code without browser dependencies. Updates all import paths across the codebase (~47 files). ## Motivation This refactoring eliminates browser dependencies in these utilities, allowing them to be imported by Node.js contexts (like Cypress test files) without causing module load failures. This is a prerequisite for upcoming Cypress test improvements. ## Changes - Move `static/assert.ts` → `shared/assert.ts` - Move `static/rison.ts` → `shared/rison.ts` - Update `biome.json` to allow `hasOwnProperty` in `shared/` directory - Update all imports across `static/`, `lib/`, and `test/` directories (47 files changed) ## Benefits - No functional changes, purely a code reorganization - Makes these utilities accessible to both frontend and backend without circular dependencies - Enables future Cypress improvements that require these utilities in Node.js context - All tests pass ✓ (699 tests) ## Test Plan - [x] TypeScript compilation passes - [x] Linting passes - [x] All unit tests pass (699 tests) - [x] Pre-commit hooks pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
780 lines
27 KiB
TypeScript
780 lines
27 KiB
TypeScript
// Copyright (c) 2021, 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 {unwrapString} from '../../shared/assert.js';
|
|
import * as BootstrapUtils from '../bootstrap-utils.js';
|
|
import {localStorage} from '../local.js';
|
|
import {Library, LibraryVersion} from '../options.interfaces.js';
|
|
import {options} from '../options.js';
|
|
import {SentryCapture} from '../sentry.js';
|
|
import {Alert} from './alert.js';
|
|
import {Lib, WidgetState} from './libs-widget.interfaces.js';
|
|
|
|
const FAV_LIBS_STORE_KEY = 'favlibs';
|
|
const c_default_compiler_non_id = '_default_';
|
|
|
|
export type CompilerLibs = Record<string, Library>;
|
|
type LangLibs = Record<string, CompilerLibs>;
|
|
type AvailableLibs = Record<string, LangLibs>;
|
|
type LibInUse = {libId: string; versionId: string} & LibraryVersion;
|
|
|
|
type FavLibraries = Record<string, string[]>;
|
|
|
|
type PopupAlertFilter = (compiler: string, langId: string) => {title: string; content: string} | null;
|
|
|
|
type LibraryAnnotation = {
|
|
commithash?: string;
|
|
cxx11?: boolean;
|
|
machine?: string;
|
|
osabi?: string;
|
|
error?: string;
|
|
};
|
|
|
|
type LibraryBuildInfo = {
|
|
arch?: string;
|
|
compilerId?: string;
|
|
compilerType?: string;
|
|
libcxx?: string;
|
|
os?: string;
|
|
error?: string;
|
|
};
|
|
|
|
type LibraryAnnotationDetail = {
|
|
buildhash: string;
|
|
annotation: LibraryAnnotation;
|
|
buildinfo: LibraryBuildInfo;
|
|
};
|
|
|
|
class LibraryAnnotations {
|
|
private all: Record<string, LibraryAnnotationDetail[]> = {};
|
|
|
|
private async get(library: string, version: string): Promise<LibraryAnnotationDetail[]> {
|
|
const libver = `${library}/${version}`;
|
|
if (!Object.keys(this.all).includes(libver)) {
|
|
const response = await fetch(`https://conan.compiler-explorer.com/annotations/${libver}`);
|
|
this.all[libver] = (await response.json()) as LibraryAnnotationDetail[];
|
|
}
|
|
return this.all[libver];
|
|
}
|
|
|
|
async getForCompiler(library: string, version: string, compilerId: string): Promise<LibraryAnnotationDetail[]> {
|
|
const result: LibraryAnnotationDetail[] = [];
|
|
|
|
try {
|
|
const details = await this.get(library, version);
|
|
for (const detail of details) {
|
|
if (detail.buildinfo.compilerId === compilerId) {
|
|
result.push(detail);
|
|
} else if (detail.buildinfo.compilerId === 'cshared') {
|
|
result.push(detail);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
SentryCapture(e, `getForCompiler(${library}, ${version}, ${compilerId})`);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
function getCompilerName(compilerId: string): string {
|
|
if (compilerId === c_default_compiler_non_id) {
|
|
return 'compiler';
|
|
}
|
|
|
|
const compilerName = '';
|
|
for (const compiler of options.compilers) {
|
|
if (compiler.id === compilerId) {
|
|
return compiler.name;
|
|
}
|
|
}
|
|
|
|
return compilerName;
|
|
}
|
|
|
|
function shortenMachineName(name: string): string {
|
|
if (name === 'Advanced Micro Devices X86-64') {
|
|
return 'amd64';
|
|
}
|
|
if (name === 'Intel 80386') {
|
|
return '386';
|
|
}
|
|
if (name === '') {
|
|
return 'default target';
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
const lib_annotations = new LibraryAnnotations();
|
|
|
|
export class LibsWidget {
|
|
private domRoot: JQuery;
|
|
|
|
private currentLangId: string;
|
|
private currentCompilerId: string;
|
|
|
|
private dropdownButton: JQuery;
|
|
private searchResults: JQuery;
|
|
|
|
private readonly onChangeCallback: () => void;
|
|
|
|
private readonly availableLibs: AvailableLibs;
|
|
private readonly filters: PopupAlertFilter[] = [
|
|
(compilerId, langId) => {
|
|
if (langId === 'rust' && ['beta', 'nightly'].includes(compilerId)) {
|
|
return {
|
|
title: 'Missing library support for rustc nightly/beta',
|
|
content:
|
|
'Compiler Explorer does not yet support libraries for the rustc nightly/beta compilers.' +
|
|
'For library support, please use the stable compiler. Please see tracking issue' +
|
|
'<a href="https://github.com/compiler-explorer/compiler-explorer/issues/3766">' +
|
|
'compiler-explorer/compiler-explorer#3766</a> for more information.',
|
|
};
|
|
}
|
|
return null;
|
|
},
|
|
];
|
|
|
|
constructor(
|
|
langId: string,
|
|
compiler: any,
|
|
dropdownButton: JQuery,
|
|
state: WidgetState,
|
|
onChangeCallback: () => void,
|
|
possibleLibs: CompilerLibs,
|
|
) {
|
|
this.dropdownButton = dropdownButton;
|
|
if (compiler) {
|
|
this.currentCompilerId = compiler.id;
|
|
} else {
|
|
this.currentCompilerId = c_default_compiler_non_id;
|
|
}
|
|
this.currentLangId = langId;
|
|
this.domRoot = $('#library-selection').clone(true);
|
|
this.initButtons();
|
|
this.onChangeCallback = onChangeCallback;
|
|
this.availableLibs = {};
|
|
this.updateAvailableLibs(possibleLibs, true);
|
|
this.loadState(state);
|
|
|
|
this.fullRefresh();
|
|
|
|
const searchInput = this.domRoot.find('.lib-search-input');
|
|
|
|
if (window.compilerExplorerOptions.mobileViewer) {
|
|
this.domRoot.addClass('mobile');
|
|
}
|
|
|
|
BootstrapUtils.setElementEventHandler(this.domRoot, 'shown.bs.modal', () => {
|
|
searchInput.trigger('focus');
|
|
|
|
for (const filter of this.filters) {
|
|
const filterResult = filter(this.currentCompilerId, this.currentLangId);
|
|
if (filterResult !== null) {
|
|
const alertSystem = new Alert();
|
|
alertSystem.notify(`${filterResult.title}: ${filterResult.content}`, {
|
|
group: 'libs',
|
|
alertClass: 'notification-error',
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
BootstrapUtils.setElementEventHandler(this.domRoot, 'hide.bs.modal', () => {
|
|
this.hidePopups();
|
|
});
|
|
|
|
searchInput.on('input', this.startSearching.bind(this));
|
|
|
|
this.domRoot.find('.lib-search-button').on('click', this.startSearching.bind(this));
|
|
|
|
this.dropdownButton.on('click', () => {
|
|
BootstrapUtils.showModal(this.domRoot);
|
|
});
|
|
|
|
this.updateButton();
|
|
}
|
|
|
|
onChange() {
|
|
this.updateButton();
|
|
this.onChangeCallback();
|
|
}
|
|
|
|
loadState(state: WidgetState) {
|
|
// If state exists, clear previously selected libraries.
|
|
if (state.libs !== undefined) {
|
|
const libsInUse = this.listUsedLibs();
|
|
for (const libId in libsInUse) {
|
|
this.markLibrary(libId, libsInUse[libId], false);
|
|
}
|
|
}
|
|
|
|
for (const lib of state.libs ?? []) {
|
|
if (lib.name && lib.ver) {
|
|
this.markLibrary(lib.name, lib.ver, true);
|
|
} else if (lib.id && lib.version) {
|
|
this.markLibrary(lib.id, lib.version, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
initButtons() {
|
|
this.searchResults = this.domRoot.find('.lib-results-items');
|
|
}
|
|
|
|
fullRefresh() {
|
|
this.showSelectedLibs();
|
|
this.showSelectedLibsAsSearchResults();
|
|
this.showFavorites();
|
|
}
|
|
|
|
updateButton() {
|
|
const selectedLibs = this.get();
|
|
let text = 'Libraries';
|
|
if (selectedLibs.length > 0) {
|
|
this.dropdownButton
|
|
.addClass('btn-success')
|
|
.removeClass('btn-light')
|
|
.prop('title', 'Current libraries:\n' + selectedLibs.map(lib => '- ' + lib.name).join('\n'));
|
|
text += ' (' + selectedLibs.length + ')';
|
|
} else {
|
|
this.dropdownButton.removeClass('btn-success').addClass('btn-light').prop('title', 'Include libs');
|
|
}
|
|
|
|
this.dropdownButton.find('.dp-text').text(text);
|
|
}
|
|
|
|
getFavorites(): FavLibraries {
|
|
return JSON.parse(localStorage.get(FAV_LIBS_STORE_KEY, '{}'));
|
|
}
|
|
|
|
setFavorites(faves: FavLibraries) {
|
|
localStorage.set(FAV_LIBS_STORE_KEY, JSON.stringify(faves));
|
|
}
|
|
|
|
isAFavorite(libId: string, versionId: string): boolean {
|
|
const faves = this.getFavorites();
|
|
if (libId in faves) {
|
|
return faves[libId].includes(versionId);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
addToFavorites(libId: string, versionId: string) {
|
|
const faves = this.getFavorites();
|
|
if (libId in faves) {
|
|
faves[libId].push(versionId);
|
|
} else {
|
|
faves[libId] = [];
|
|
faves[libId].push(versionId);
|
|
}
|
|
|
|
this.setFavorites(faves);
|
|
}
|
|
|
|
removeFromFavorites(libId: string, versionId: string) {
|
|
const faves = this.getFavorites();
|
|
if (libId in faves) {
|
|
faves[libId] = faves[libId].filter(v => v !== versionId);
|
|
}
|
|
|
|
this.setFavorites(faves);
|
|
}
|
|
|
|
newFavoriteLibDiv(libId: string, versionId: string, lib: Library, version: LibraryVersion): JQuery<HTMLElement> {
|
|
const template = $('#lib-favorite-tpl');
|
|
|
|
const libDiv = $(template.children().eq(0).clone());
|
|
|
|
const quickSelectButton = libDiv.find('.lib-name-and-version');
|
|
quickSelectButton.html(lib.name + ' ' + version.version);
|
|
quickSelectButton.on('click', () => {
|
|
this.selectLibAndVersion(libId, versionId);
|
|
this.showSelectedLibs();
|
|
this.onChange();
|
|
});
|
|
|
|
return libDiv;
|
|
}
|
|
|
|
showFavorites() {
|
|
const favoritesDiv = this.domRoot.find('.lib-favorites');
|
|
favoritesDiv.html('');
|
|
|
|
const faves = this.getFavorites();
|
|
for (const libId in faves) {
|
|
const versionArr = faves[libId];
|
|
for (const versionId of versionArr) {
|
|
const lib = this.getLibInfoById(libId);
|
|
if (lib) {
|
|
if (versionId in lib.versions) {
|
|
const version = lib.versions[versionId];
|
|
const div = this.newFavoriteLibDiv(libId, versionId, lib, version);
|
|
favoritesDiv.append(div);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
hidePopups() {
|
|
this.searchResults.find('.lib-info-button').each((_, el) => BootstrapUtils.hidePopover($(el)));
|
|
}
|
|
|
|
clearSearchResults() {
|
|
this.searchResults.find('.lib-info-button').each((_, el) => {
|
|
const popover = BootstrapUtils.getPopoverInstance($(el));
|
|
if (popover) popover.dispose();
|
|
});
|
|
this.searchResults.html('');
|
|
}
|
|
|
|
newSelectedLibDiv(libId: string, versionId: string, lib: Library, version: LibraryVersion): JQuery<HTMLElement> {
|
|
const template = $('#lib-selected-tpl');
|
|
|
|
const libDiv = $(template.children().eq(0).clone());
|
|
|
|
const detailsButton = libDiv.find('.lib-name-and-version');
|
|
detailsButton.html(lib.name + ' ' + version.version);
|
|
detailsButton.on('click', () => {
|
|
this.clearSearchResults();
|
|
this.addSearchResult(libId, lib);
|
|
});
|
|
|
|
const deleteButton = libDiv.find('.lib-remove');
|
|
deleteButton.on('click', () => {
|
|
this.markLibrary(libId, versionId, false);
|
|
libDiv.remove();
|
|
this.showSelectedLibs();
|
|
this.onChange();
|
|
// We need to refresh the library lists, or the selector will still show up with the old library version
|
|
this.startSearching();
|
|
});
|
|
|
|
return libDiv;
|
|
}
|
|
|
|
conjureUpExamples(result: JQuery<HTMLElement>, lib: Library) {
|
|
const examples = result.find('.lib-examples');
|
|
if (lib.examples && lib.examples.length > 0) {
|
|
examples.append($('<b>Examples</b>'));
|
|
const examplesList = $('<ul />');
|
|
for (const exampleId of lib.examples) {
|
|
const li = $('<li />');
|
|
examplesList.append(li);
|
|
const exampleLink = $('<a>Example</a>');
|
|
exampleLink.attr('href', `${window.httpRoot}z/${exampleId}`);
|
|
exampleLink.attr('target', '_blank');
|
|
exampleLink.attr('rel', 'noopener');
|
|
li.append(exampleLink);
|
|
}
|
|
examples.append(examplesList);
|
|
}
|
|
}
|
|
|
|
async getBuildInfoAsHtml(libId: string, semver: string, url?: string): Promise<string> {
|
|
const details = await lib_annotations.getForCompiler(libId, semver, this.currentCompilerId);
|
|
|
|
let libInfoText = '';
|
|
for (const info of details) {
|
|
if (info.annotation.commithash) {
|
|
const machineName = shortenMachineName(info.annotation.machine || '');
|
|
const stdlib = info.buildinfo.libcxx;
|
|
if (url?.startsWith('https://github.com/')) {
|
|
// this is a bit of a hack because we don't store the git repo in our properties files
|
|
libInfoText +=
|
|
`<li>Binary for ${machineName} (${stdlib}) based on commit: ` +
|
|
`<a href="${url}/commit/${info.annotation.commithash}" target="_blank">` +
|
|
info.annotation.commithash +
|
|
'</a></li>';
|
|
} else {
|
|
libInfoText +=
|
|
`<li>Binary for ${machineName} (${stdlib}) based on commit: ` +
|
|
info.annotation.commithash +
|
|
'</li>';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!libInfoText) {
|
|
libInfoText = 'No binaries available';
|
|
} else {
|
|
libInfoText = '<ul>' + libInfoText + '</ul>';
|
|
}
|
|
|
|
return libInfoText;
|
|
}
|
|
|
|
async loadBuildInfoIntoPopup(popupId: string, libId: string, semver: string, url?: string) {
|
|
const libInfoText = await this.getBuildInfoAsHtml(libId, semver, url);
|
|
$('#' + popupId).html(libInfoText);
|
|
}
|
|
|
|
newSearchResult(libId: string, lib: Library): JQuery<HTMLElement> {
|
|
const template = $('#lib-search-result-tpl');
|
|
|
|
const result = $(template.children().eq(0).clone());
|
|
result.find('.lib-name').html(lib.name || libId);
|
|
if (!lib.description) {
|
|
result.find('.lib-description').hide();
|
|
} else {
|
|
result.find('.lib-description').html(lib.description);
|
|
}
|
|
result.find('.lib-website-link').attr('href', lib.url ?? '#');
|
|
|
|
this.conjureUpExamples(result, lib);
|
|
|
|
const faveButton = result.find('.lib-fav-button');
|
|
const faveStar = faveButton.find('.lib-fav-btn-icon');
|
|
const infoButton = result.find('.lib-info-button');
|
|
faveButton.hide();
|
|
infoButton.hide();
|
|
|
|
const versions = result.find('.lib-version-select');
|
|
versions.html('');
|
|
const noVersionSelectedOption = $('<option value="">-</option>');
|
|
versions.append(noVersionSelectedOption);
|
|
let hasVisibleVersions = false;
|
|
|
|
const versionsArr = Object.keys(lib.versions).map(id => {
|
|
return {id: id, order: lib.versions[id]['$order']};
|
|
});
|
|
versionsArr.sort((a, b) => b.order - a.order);
|
|
|
|
for (const libVersion of versionsArr) {
|
|
const versionId = libVersion.id;
|
|
const version = lib.versions[versionId];
|
|
const option = $('<option>');
|
|
if (version.used) {
|
|
option.attr('selected', 'selected');
|
|
|
|
if (this.isAFavorite(libId, versionId)) {
|
|
faveStar.removeClass('far').addClass('fas');
|
|
}
|
|
|
|
faveButton.show();
|
|
infoButton.show();
|
|
}
|
|
option.attr('value', versionId);
|
|
option.html(version.version || versionId);
|
|
|
|
option.data('lookupname', version.lookupname || libId);
|
|
option.data('lookupversion', version.lookupversion || version.version || versionId);
|
|
|
|
if (version.used || !version.hidden) {
|
|
hasVisibleVersions = true;
|
|
versions.append(option);
|
|
}
|
|
}
|
|
|
|
if (!hasVisibleVersions) {
|
|
noVersionSelectedOption.text('No available versions');
|
|
versions.prop('disabled', true);
|
|
}
|
|
|
|
const popoverTemplate =
|
|
'<div class="popover" role="tooltip">' +
|
|
'<div class="arrow"></div>' +
|
|
'<h3 class="popover-header"></h3><div class="popover-body"></div>' +
|
|
'</div>';
|
|
BootstrapUtils.initPopover(infoButton, {
|
|
html: true,
|
|
title: 'Build info for ' + getCompilerName(this.currentCompilerId),
|
|
content: () => {
|
|
const nowts = Math.round(Date.now() / 1000);
|
|
const popupId = `build-info-content-${nowts}`;
|
|
const option = versions.find('option:selected');
|
|
const semver = option.html();
|
|
const lookupname = option.data('lookupname');
|
|
const lookupversion = option.data('lookupversion');
|
|
if (semver !== '-') {
|
|
this.loadBuildInfoIntoPopup(popupId, lookupname, lookupversion, lib.url);
|
|
return `<div id="${popupId}">Loading...</div>`;
|
|
}
|
|
return `<div id="${popupId}">No version selected</div>`;
|
|
},
|
|
template: popoverTemplate,
|
|
customClass: 'library-info-popover',
|
|
});
|
|
|
|
faveButton.on('click', () => {
|
|
const option = versions.find('option:selected');
|
|
const verId = option.attr('value') as string;
|
|
if (this.isAFavorite(libId, verId)) {
|
|
this.removeFromFavorites(libId, verId);
|
|
faveStar.removeClass('fas').addClass('far');
|
|
} else {
|
|
this.addToFavorites(libId, verId);
|
|
faveStar.removeClass('far').addClass('fas');
|
|
}
|
|
this.showFavorites();
|
|
});
|
|
|
|
versions.on('change', () => {
|
|
const option = versions.find('option:selected');
|
|
const verId = option.attr('value') as string;
|
|
|
|
this.selectLibAndVersion(libId, verId);
|
|
this.showSelectedLibs();
|
|
|
|
if (this.isAFavorite(libId, verId)) {
|
|
faveStar.removeClass('far').addClass('fas');
|
|
} else {
|
|
faveStar.removeClass('fas').addClass('far');
|
|
}
|
|
|
|
// Is this the "No selection" option?
|
|
if (verId.length > 0) {
|
|
faveButton.show();
|
|
infoButton.show();
|
|
} else {
|
|
faveButton.hide();
|
|
infoButton.hide();
|
|
}
|
|
|
|
this.onChange();
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
addSearchResult(libId: string, library: Library) {
|
|
const result = this.newSearchResult(libId, library);
|
|
this.searchResults.append(result);
|
|
}
|
|
|
|
static _libVersionMatchesQuery(library: Library, searchText: string): boolean {
|
|
const text = searchText.toLowerCase();
|
|
return library.name?.toLowerCase().includes(text) || library.description?.toLowerCase().includes(text) || false;
|
|
}
|
|
|
|
startSearching() {
|
|
const searchText = unwrapString(this.domRoot.find('.lib-search-input').val());
|
|
|
|
this.clearSearchResults();
|
|
|
|
const currentAvailableLibs = this.availableLibs[this.currentLangId][this.currentCompilerId];
|
|
if (Object.keys(currentAvailableLibs).length === 0) {
|
|
const nolibsMessage = $('#libs-dropdown').children().eq(0).clone();
|
|
this.searchResults.append(nolibsMessage);
|
|
return;
|
|
}
|
|
|
|
for (const libId in currentAvailableLibs) {
|
|
const library = currentAvailableLibs[libId];
|
|
|
|
if ('autodetect' in library.versions) continue;
|
|
|
|
if (LibsWidget._libVersionMatchesQuery(library, searchText)) {
|
|
this.addSearchResult(libId, library);
|
|
}
|
|
}
|
|
}
|
|
|
|
showSelectedLibs() {
|
|
const items = this.domRoot.find('.libs-selected-items');
|
|
items.html('');
|
|
|
|
const selectedLibs = this.listUsedLibs();
|
|
for (const libId in selectedLibs) {
|
|
const versionId = selectedLibs[libId];
|
|
|
|
const lib = this.availableLibs[this.currentLangId][this.currentCompilerId][libId];
|
|
const version = lib.versions[versionId];
|
|
|
|
const libDiv = this.newSelectedLibDiv(libId, versionId, lib, version);
|
|
items.append(libDiv);
|
|
}
|
|
}
|
|
|
|
showSelectedLibsAsSearchResults() {
|
|
this.clearSearchResults();
|
|
|
|
const currentAvailableLibs = this.availableLibs[this.currentLangId][this.currentCompilerId];
|
|
if (Object.keys(currentAvailableLibs).length === 0) {
|
|
const nolibsMessage = $('#libs-dropdown').children().eq(0).clone();
|
|
this.searchResults.append(nolibsMessage);
|
|
return;
|
|
}
|
|
|
|
for (const libId in currentAvailableLibs) {
|
|
const library = currentAvailableLibs[libId];
|
|
|
|
if ('autodetect' in library.versions) continue;
|
|
|
|
const card = this.newSearchResult(libId, library);
|
|
this.searchResults.append(card);
|
|
}
|
|
}
|
|
|
|
initLangDefaultLibs() {
|
|
const defaultLibs = options.defaultLibs[this.currentLangId];
|
|
if (!defaultLibs) return;
|
|
for (const libPair of defaultLibs.split(':')) {
|
|
const pairSplits = libPair.split('.');
|
|
if (pairSplits.length === 2) {
|
|
const lib = pairSplits[0];
|
|
const ver = pairSplits[1];
|
|
this.markLibrary(lib, ver, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
updateAvailableLibs(possibleLibs: CompilerLibs, isLangChanged: boolean) {
|
|
if (!(this.currentLangId in this.availableLibs)) {
|
|
this.availableLibs[this.currentLangId] = {};
|
|
}
|
|
|
|
if (!(this.currentCompilerId in this.availableLibs[this.currentLangId])) {
|
|
if (this.currentCompilerId === '_default_') {
|
|
this.availableLibs[this.currentLangId][this.currentCompilerId] = $.extend(
|
|
true,
|
|
{},
|
|
options.libs[this.currentLangId],
|
|
);
|
|
} else {
|
|
this.availableLibs[this.currentLangId][this.currentCompilerId] = $.extend(true, {}, possibleLibs);
|
|
}
|
|
}
|
|
|
|
if (isLangChanged) {
|
|
this.initLangDefaultLibs();
|
|
}
|
|
}
|
|
|
|
setNewLangId(langId: string, compilerId: string, possibleLibs: CompilerLibs) {
|
|
const libsInUse = this.listUsedLibs();
|
|
|
|
const isLangChanged = this.currentLangId !== langId;
|
|
|
|
this.currentLangId = langId;
|
|
|
|
if (compilerId) {
|
|
this.currentCompilerId = compilerId;
|
|
} else {
|
|
this.currentCompilerId = '_default_';
|
|
}
|
|
|
|
// Clear the dom Root so it gets rebuilt with the new language libraries
|
|
this.updateAvailableLibs(possibleLibs, isLangChanged);
|
|
|
|
for (const libId in libsInUse) {
|
|
this.markLibrary(libId, libsInUse[libId], true);
|
|
}
|
|
|
|
this.fullRefresh();
|
|
this.onChange();
|
|
}
|
|
|
|
getVersionOrAlias(name: string, versionId: string): string | null {
|
|
const lib = this.getLibInfoById(name);
|
|
if (!lib) return null;
|
|
// If it's already a key, return it directly
|
|
if (versionId in lib.versions) {
|
|
return versionId;
|
|
}
|
|
// Else, look in each version and see if it has the id as an alias
|
|
for (const verId in lib.versions) {
|
|
const version = lib.versions[verId];
|
|
if (version.alias.includes(versionId)) {
|
|
return verId;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getLibInfoById(libId: string): Library | undefined {
|
|
if (
|
|
this.currentLangId in this.availableLibs &&
|
|
this.currentCompilerId in this.availableLibs[this.currentLangId] &&
|
|
libId in this.availableLibs[this.currentLangId][this.currentCompilerId]
|
|
) {
|
|
return this.availableLibs[this.currentLangId][this.currentCompilerId][libId];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
markLibrary(name: string, versionId: string, used: boolean) {
|
|
const actualId = this.getVersionOrAlias(name, versionId);
|
|
if (actualId != null) {
|
|
const v = this.getLibInfoById(name)?.versions[actualId];
|
|
if (v != null) {
|
|
v.used = used;
|
|
}
|
|
}
|
|
}
|
|
|
|
selectLibAndVersion(libId: string, versionId: string) {
|
|
const actualId = this.getVersionOrAlias(libId, versionId);
|
|
const libInfo = this.getLibInfoById(libId);
|
|
if (libInfo) {
|
|
for (const v in libInfo.versions) {
|
|
const version = libInfo.versions[v];
|
|
version.used = v === actualId;
|
|
}
|
|
}
|
|
}
|
|
|
|
get(): Lib[] {
|
|
const result: Lib[] = [];
|
|
const usedLibs = this.listUsedLibs();
|
|
for (const libId in usedLibs) {
|
|
result.push({name: libId, ver: usedLibs[libId]});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
listUsedLibs(): Record<string, string> {
|
|
const libs: Record<string, string> = {};
|
|
const currentAvailableLibs = this.availableLibs[this.currentLangId][this.currentCompilerId];
|
|
for (const libId in currentAvailableLibs) {
|
|
const library = currentAvailableLibs[libId];
|
|
for (const verId in library.versions) {
|
|
if (library.versions[verId].used) {
|
|
libs[libId] = verId;
|
|
}
|
|
}
|
|
}
|
|
return libs;
|
|
}
|
|
|
|
getLibsInUse(): LibInUse[] {
|
|
const libs: LibInUse[] = [];
|
|
const currentAvailableLibs = this.availableLibs[this.currentLangId][this.currentCompilerId];
|
|
for (const libId in currentAvailableLibs) {
|
|
const library = currentAvailableLibs[libId];
|
|
for (const verId in library.versions) {
|
|
if (library.versions[verId].used) {
|
|
libs.push({...library.versions[verId], libId: libId, versionId: verId});
|
|
}
|
|
}
|
|
}
|
|
return libs;
|
|
}
|
|
}
|