/* firmwareUpdateModal.tsx - ESP3D WebUI component file Copyright (c) 2025 Mike Melancon. All rights reserved. This code is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with This code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ import { useUiContextFn } from "../../contexts" import type { GitHubRelease } from "../../types/github.types" import type { ModalManager } from "../../types/modals.types" import { Download } from "preact-feather" interface FirmwareUpdateModalParams { modals: ModalManager releases: GitHubRelease[] currentVersion: string onUploadFile: () => void onViewReleaseNotes: () => void } const showFirmwareUpdateModal = ({ modals, releases, currentVersion, onUploadFile, onViewReleaseNotes, }: FirmwareUpdateModalParams): void => { let selectedReleaseIndex = 0 let showPrereleases = false const getFilteredReleases = (): GitHubRelease[] => { return showPrereleases ? releases : releases.filter((r) => !r.prerelease) } const updateReleaseSelector = () => { const filteredReleases = getFilteredReleases() const selector = document.getElementById("firmware-release-selector") as HTMLSelectElement const infoBox = document.getElementById("firmware-selected-release-info") if (!selector || !infoBox) return // Clear and repopulate selector selector.innerHTML = "" filteredReleases.forEach((release, index) => { const option = document.createElement("option") option.value = index.toString() option.text = release.name + (index === 0 ? " (Latest)" : "") selector.appendChild(option) }) // Reset to first release selectedReleaseIndex = 0 selector.value = "0" // Update info box if (filteredReleases.length > 0) { const selectedRelease = filteredReleases[0] infoBox.innerHTML = `${selectedRelease.tag_name} - Released ${new Date( selectedRelease.published_at ).toLocaleDateString()}` } // Update download buttons updateDownloadButtons() } const updateDownloadButtons = () => { const filteredReleases = getFilteredReleases() const win64Button = document.getElementById("firmware-download-win64") as HTMLButtonElement const posixButton = document.getElementById("firmware-download-posix") as HTMLButtonElement if (!win64Button || !posixButton || filteredReleases.length === 0) return const selectedRelease = filteredReleases[selectedReleaseIndex] const hasWin64 = selectedRelease.assets.some((a) => a.name.includes("win64")) const hasPosix = selectedRelease.assets.some((a) => a.name.includes("posix")) win64Button.disabled = !hasWin64 posixButton.disabled = !hasPosix } const downloadFirmware = (platform: "win64" | "posix") => { useUiContextFn.haptic() const filteredReleases = getFilteredReleases() if (filteredReleases.length === 0) return const selectedRelease = filteredReleases[selectedReleaseIndex] const asset = selectedRelease.assets.find((a) => a.name.includes(platform)) if (!asset) { console.error(`${platform} asset not found in release ${selectedRelease.tag_name}`) return } // Trigger browser download const link = document.createElement("a") link.href = asset.browser_download_url link.download = asset.name document.body.appendChild(link) link.click() document.body.removeChild(link) } const handleUploadFile = (e?: Event) => { if (e) e.stopPropagation() useUiContextFn.haptic() const modalIndex = modals.getModalIndex("firmware-update") if (modalIndex !== -1) { modals.removeModal(modalIndex) } onUploadFile() } const handleViewReleaseNotes = (e?: Event) => { if (e) e.stopPropagation() useUiContextFn.haptic() onViewReleaseNotes() } const closeModal = (e?: Event) => { if (e) e.stopPropagation() useUiContextFn.haptic() const modalIndex = modals.getModalIndex("firmware-update") if (modalIndex !== -1) { modals.removeModal(modalIndex) } } const compareVersions = (current: string, target: string): string => { // Remove 'FluidNC ' prefix, 'v' prefix and any prerelease suffix // Examples: "FluidNC v3.9.9" -> "3.9.9", "v3.9.9" -> "3.9.9", "v3.7-rc4" -> "3.7" const cleanCurrent = current.replace(/^FluidNC\s+/i, "").replace(/^v/, "").split(/[-_]/)[0] const cleanTarget = target.replace(/^FluidNC\s+/i, "").replace(/^v/, "").split(/[-_]/)[0] const currentParts = cleanCurrent.split(".").map(Number) const targetParts = cleanTarget.split(".").map(Number) for (let i = 0; i < Math.max(currentParts.length, targetParts.length); i++) { const currentPart = currentParts[i] || 0 const targetPart = targetParts[i] || 0 if (currentPart < targetPart) return "upgrade" if (currentPart > targetPart) return "downgrade" } return "same" } const filteredReleases = getFilteredReleases() const latestRelease = filteredReleases.length > 0 ? filteredReleases[0] : null if (modals.getModalIndex("firmware-update") === -1) { modals.addModal({ id: "firmware-update", title: (
), content: (