/* 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: (
{/* Version comparison box */}
Current Version {currentVersion}
{latestRelease && ( <>
{compareVersions(currentVersion, latestRelease.tag_name) === "upgrade" && ( Upgrade Available )} {compareVersions(currentVersion, latestRelease.tag_name) === "same" && ( Up to date )} {compareVersions(currentVersion, latestRelease.tag_name) === "downgrade" && ( Downgrade )}
Latest Release {latestRelease.tag_name} {latestRelease.prerelease && ( (Pre-release) )}
)}
{/* Prerelease toggle */}
{/* Release selector */}
{filteredReleases[0]?.tag_name} - Released{" "} {new Date(filteredReleases[0]?.published_at || "").toLocaleDateString()}

{/* Download instructions */}
Download Instructions:
  1. Choose your platform below and click download
  2. Extract the downloaded zip file
  3. Locate the .bin file inside the extracted folder
  4. Return to previous dialog to upload the .bin to your device
{/* Download buttons */}
{/*
*/} {/* Action button */} {/*
*/}
), // footer: ( //
// //
// ), overlay: undefined, hideclose: false, }) } } export { showFirmwareUpdateModal } export type { FirmwareUpdateModalParams }