From 50ad5e63fbbbd069f2f525d459c5ffe71f2ccc32 Mon Sep 17 00:00:00 2001 From: Dan Rosser Date: Mon, 5 Oct 2026 15:03:43 +1100 Subject: [PATCH 1/3] Downloads tab: core libs by release tag, modular libs with per-platform status --- frontend/app.js | 244 ++++++++++++++++++++++++++-- frontend/index.html | 69 +++++++- frontend/index.js | 290 +++++++++++++++++++++++++++++++++- frontend/locales/de.json | 37 ++++- frontend/locales/en.json | 37 ++++- frontend/locales/es.json | 37 ++++- frontend/locales/fr.json | 37 ++++- frontend/locales/ja.json | 37 ++++- frontend/locales/ko.json | 37 ++++- frontend/locales/pt.json | 37 ++++- frontend/locales/ru.json | 37 ++++- frontend/locales/zh-CN.json | 37 ++++- frontend/static/css/index.css | 5 +- 13 files changed, 886 insertions(+), 55 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index fef36728..65726fd4 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -10,7 +10,7 @@ let templates; let lastGeneratedProject = null; // var platforms = { -// "osx": "OS X (Xcode)", +// "osx": "macOS (OSX) (Xcode)", // "vs": "Windows (Visual Studio)", // "ios": "iOS (Xcode)", // "linux": "Linux 32-bit (Code::Blocks)", @@ -68,7 +68,7 @@ function applyTranslations() { const rescanLink = '' + t('msgBox.missingAddons.linkText') + ''; $('#missingAddonBody2').html(t('msgBox.missingAddons.body2Suffix', { link: rescanLink })); - $('#ofMenuDescription').html(t('ofMenu.description', { script: 'scripts/of.sh' })); + if (libReleases) renderModularLibs(); $('#ofPathSierraEasyFix').html(`${t('msgBox.sierra.easyFixLabel')} ${t('msgBox.sierra.easyFix')}`); $('#ofPathSierraPermanentFix').html(`${t('msgBox.sierra.permanentFixLabel')} ${t('msgBox.sierra.permanentFix')}`); @@ -669,6 +669,31 @@ function setup() { } }); + $('#coreReleaseDropdown, #corePlatformDropdown').dropdown(); + $('#modularReleaseDropdown').dropdown({ onChange: () => fillModularTargets() }); + $('#modularTargetDropdown').dropdown({ onChange: () => renderModularLibs() }); + $('#modularSelectAll').on('change', function () { + const release = currentModularRelease(); + const target = $('#modularTargetDropdown').dropdown('get value'); + modularSelection = new Set(this.checked && release ? release.modular.filter((l) => l.target === target).map((l) => l.asset) : []); + renderModularLibs(); + }); + + $("#ofMenuButton").tab({ + 'onVisible': () => { + if (isOfPathGood !== true) { + $('#settingsMenuButton').click(); + $('#ofPathError').modal({ + onHide: () => { + $('#settingsMenuButton').click(); + } + }).modal("show"); + return; + } + loadLibReleases(false); + } + }); + $("#settingsMenuButton").tab({ 'onVisible': () => { console.log("settings!! "); @@ -856,6 +881,11 @@ function setup() { }); // the console dock is always present as a collapsed peek strip - only + // keep the page scrollable past the console at any height + new ResizeObserver(([entry]) => { + document.documentElement.style.setProperty('--console-height', entry.target.offsetHeight + 'px'); + }).observe(document.getElementById('consoleContainer')); + // whether it's expanded (showConsole) is a persisted preference if (defaultSettings['showConsole']) { $("body").addClass('showConsole'); } $("#showConsole").on('click', function(){ @@ -1613,18 +1643,214 @@ ipcRenderer.on('buildEmscriptenDone', () => { $("#EmscriptenPreviewButton").removeClass('loading disabled').text(t('button.buildPreview')); }); -function ofMenuButtonFor(command) { - return command === 'status' ? $('#ofMenuStatusButton') : $('#ofMenuUpdateLibsButton'); -} - function runOfMenuCommand(command) { - ofMenuButtonFor(command).addClass('loading disabled'); + $('#ofMenuStatusButton').addClass('loading disabled'); openConsoleForOperation(); ipcRenderer.send('runOfMenu', { command, ofPath: $("#ofPath").val() }); } -ipcRenderer.on('ofMenuDone', (event, { command }) => { - ofMenuButtonFor(command).removeClass('loading disabled'); +ipcRenderer.on('ofMenuDone', () => { + $('#ofMenuStatusButton').removeClass('loading disabled'); +}); + +//---------------------------------------- +// downloads tab +let libReleases = null; +let modularSelection = new Set(); + +function loadLibReleases(refresh) { + $('#refreshLibReleasesButton').addClass('loading disabled'); + $('#libReleasesError').hide(); + ipcRenderer.send('getLibReleases', { ofPath: $("#ofPath").val(), refresh: !!refresh }); +} + +function formatSize(bytes) { + return bytes >= 1048576 ? (bytes / 1048576).toFixed(1) + ' MB' : Math.max(1, Math.round(bytes / 1024)) + ' KB'; +} + +function releaseLabel(r) { + const date = r.published ? ' (' + r.published.slice(0, 10) + ')' : ''; + return r.tag + date + (r.prerelease ? ' - ' + t('downloads.prerelease') : ''); +} + +function fillDropdown(selector, values, selected, labelFor) { + const menu = $(selector + ' .menu'); + menu.empty(); + for (const v of values) { + menu.append($('
').attr('data-value', v).text(labelFor ? labelFor(v) : v)); + } + $(selector).dropdown('refresh'); + if (selected != null && values.includes(selected)) { + $(selector).dropdown('set selected', selected); + } +} + +ipcRenderer.on('libReleases', (event, data) => { + $('#refreshLibReleasesButton').removeClass('loading disabled'); + if (data.error) { + $('#libReleasesError').text(t('downloads.releasesError', { error: data.error })).show(); + return; + } + const previous = libReleases; + libReleases = data; + + const byTag = {}; + for (const r of data.releases) byTag[r.tag] = r; + const coreTags = data.releases.filter((r) => r.hasCore).map((r) => r.tag); + const modularTags = data.releases.filter((r) => r.modular.length).map((r) => r.tag); + const keep = (sel, fallback, list) => { + const cur = previous ? $(sel).dropdown('get value') : ''; + return list.includes(cur) ? cur : fallback; + }; + + fillDropdown('#coreReleaseDropdown', coreTags, keep('#coreReleaseDropdown', 'latest', coreTags), (tag) => releaseLabel(byTag[tag])); + const corePlatforms = sortCorePlatforms(data.corePlatforms); + fillDropdown('#corePlatformDropdown', corePlatforms, keep('#corePlatformDropdown', data.hostPlatform, corePlatforms), corePlatformLabel); + fillDropdown('#modularReleaseDropdown', modularTags, keep('#modularReleaseDropdown', 'latest-modular', modularTags), (tag) => releaseLabel(byTag[tag])); + fillModularTargets(); +}); + +function currentModularRelease() { + const tag = $('#modularReleaseDropdown').dropdown('get value'); + return libReleases && libReleases.releases.find((r) => r.tag === tag); +} + +// scripts//download_libs.sh folder -> readable name, in dropdown order +const CORE_PLATFORM_LABELS = { + osx: 'macOS (OSX only)', + macos: 'macOS (including iOS, tvOS, catOS)', + ios: 'iOS', + tvos: 'tvOS', + vs: 'Windows (Visual Studio)', + msys2: 'Windows (MSYS2)', + linux: 'Linux', + linux64: 'Linux (x86_64)', + linuxaarch64: 'Linux (arm64)', + linuxarmv6l: 'Linux (armv6l)', + linuxarmv7l: 'Linux (armv7l)', + android: 'Android', + emscripten: 'Emscripten (Web)', +}; + +function corePlatformLabel(platform) { + return CORE_PLATFORM_LABELS[platform] || platform; +} + +function sortCorePlatforms(platforms) { + const order = Object.keys(CORE_PLATFORM_LABELS); + const rank = (p) => (order.includes(p) ? order.indexOf(p) : order.length); + return [...platforms].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b)); +} + +// osx_64 is one universal xcframework, so both archs share an entry +function modularTargetLabel(target) { + const named = { + osx_64: 'macOS (OSX) (x86_64 / arm64)', + ios_64: 'iOS (arm64)', + vs_64: 'Windows (x64)', + vs_arm64: 'Windows (arm64)', + vs_arm64ec: 'Windows (arm64ec)', + emscripten_64: 'Emscripten (wasm64)', + emscripten_32: 'Emscripten (wasm32)', + }; + if (named[target]) return named[target]; + const linux = /^linux_([^_]+)(?:_(gcc\d+))?$/.exec(target); + if (linux) { + const arch = { '64': 'x86_64', aarch64: 'arm64', arm64: 'arm64' }[linux[1]] || linux[1]; + return 'Linux (' + arch + ')' + (linux[2] ? ' ' + linux[2] : ''); + } + return target; +} + +// which installedPlatforms() name a package target fills - matches LIB_DIR_PLATFORMS in index.js +function modularTargetPlatform(target) { + if (target.startsWith('osx')) return 'macOS'; + if (target.startsWith('ios')) return 'iOS'; + if (target.startsWith('tvos')) return 'tvOS'; + if (target.startsWith('emscripten')) return 'Emscripten'; + if (target.startsWith('vs')) return 'Windows'; + const linux = /^linux_([^_]+)/.exec(target); + if (linux) return { '64': 'Linux', aarch64: 'Linux arm64', arm64: 'Linux arm64' }[linux[1]] || 'Linux ' + linux[1]; + return target; +} + +function fillModularTargets() { + const release = currentModularRelease(); + const targets = release ? [...new Set(release.modular.map((l) => l.target))].sort() : []; + const current = $('#modularTargetDropdown').dropdown('get value'); + const host = libReleases ? libReleases.hostModularTarget : ''; + const selected = targets.includes(current) ? current : (targets.includes(host) ? host : targets[0]); + fillDropdown('#modularTargetDropdown', targets, selected, modularTargetLabel); + renderModularLibs(); +} + +function renderModularLibs() { + const release = currentModularRelease(); + const target = $('#modularTargetDropdown').dropdown('get value'); + const libs = release ? release.modular.filter((l) => l.target === target) : []; + libs.sort((a, b) => a.lib.toLowerCase().localeCompare(b.lib.toLowerCase())); + + const names = new Set(libs.map((l) => l.asset)); + modularSelection = new Set([...modularSelection].filter((n) => names.has(n))); + + const body = $('#modularLibTable tbody'); + body.empty(); + if (!libs.length) { + body.append($('').append($('').text(release ? t('downloads.noneForTarget') : t('downloads.loading')))); + } + for (const l of libs) { + const checkbox = $('').prop('checked', modularSelection.has(l.asset)).on('change', function () { + if (this.checked) modularSelection.add(l.asset); else modularSelection.delete(l.asset); + updateModularButton(); + }); + const current = modularTargetPlatform(target); + const status = l.platforms.length + ? l.platforms.map((p) => $('').toggleClass('green', p === current).text(p)) + : $('').text(t('downloads.notInstalled')); + body.append($('').append( + $('').append($('
').append(checkbox, $('