Compare commits

...

2 Commits

Author SHA1 Message Date
3caae592f1 Enhance update messaging and version formatting in AppUpdateContext and appupdate.js
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Introduced a new function `formatFullAppVersion` to standardize the application version and build number representation.
- Updated the `getUpdateInstalledMessage` function to provide detailed feedback on updates, including changes in version, branch, and engine.
- Refactored the update handling logic to improve clarity and maintainability, ensuring users receive accurate and informative update notifications.
2026-08-09 14:06:04 +01:00
ba5856c248 Refactor Windows installer progress macros for improved clarity and functionality
- Introduced new macros for various installation progress stages, enhancing readability and maintainability of the installer script.
- Updated the progress reporting in the `farmcontrol.nsi` file to utilize the new macros, ensuring consistent progress tracking during installation.
- Adjusted the `winappupdate.js` file to align with the new progress constants, improving the accuracy of progress percentage calculations.
2026-08-09 14:02:35 +01:00
5 changed files with 168 additions and 22 deletions

View File

@ -67,7 +67,7 @@ Function .onInit
!insertmacro progressPhase "Starting installation"
!insertmacro progressStatus "Starting Farm Control installation..."
!insertmacro progressCopyTotal "${APP_COPY_TOTAL_BYTES}"
!insertmacro progressPercent 0
!insertmacro progressStart
; In-app updates install into Farm Control.new while the running process
; keeps using Farm Control; the folders are swapped after exit (/RESTARTFC).
@ -78,6 +78,8 @@ Function .onInit
StrCpy $FinalInstDir $INSTDIR
!insertmacro uninstallPreviousFarmControl
${EndIf}
!insertmacro progressPrepare
FunctionEnd
Function .onInstFailed
@ -93,17 +95,17 @@ Section "Farm Control" SecMain
!insertmacro progressPhase "Copying application files"
!insertmacro progressStatus "Copying application files..."
!insertmacro progressPercent 0
!insertmacro progressCopyStart
SetOverwrite try
!insertmacro copyApplicationFilesWithProgress
!insertmacro progressPercent 75
!insertmacro progressCopyComplete
!insertmacro customInstall
!insertmacro progressFinalizeInstall
!insertmacro progressPhase "Finalizing installation"
!insertmacro progressStatus "Writing uninstall information..."
!insertmacro progressPercent 90
!insertmacro progressStatus "Writing installation registry entries..."
WriteRegStr HKCU "Software\Tom Butcher\Farm Control" "InstallDir" $FinalInstDir
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" \
@ -119,8 +121,14 @@ Section "Farm Control" SecMain
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" \
"NoRepair" 1
!insertmacro progressWriteUninstaller
!insertmacro progressStatus "Writing uninstall information..."
WriteUninstaller "$INSTDIR\Uninstall.exe"
!insertmacro progressFinalize
; For in-app updates (/RESTARTFC) the success signal is emitted from
; restartFarmControlAfterUpdate, right before the install directory swap,
; so the running app quits while this installer stays alive.

View File

@ -95,10 +95,56 @@ Var UpdateParentPid
${EndIf}
!macroend
!define PROGRESS_START 1
!define PREPARE_PROGRESS 3
!define COPY_PROGRESS_START 5
!define COPY_PROGRESS_END 90
!define CONFIGURE_PROGRESS 91
!define SHORTCUTS_PROGRESS 93
!define FINALIZE_PROGRESS 95
!define UNINSTALLER_PROGRESS 97
!define COMPLETE_PROGRESS 99
!macro progressPercent percent
!insertmacro progressLog "installer:%${percent}"
!macroend
!macro progressStart
!insertmacro progressPercent "${PROGRESS_START}"
!macroend
!macro progressPrepare
!insertmacro progressPercent "${PREPARE_PROGRESS}"
!macroend
!macro progressCopyStart
!insertmacro progressPercent "${COPY_PROGRESS_START}"
!macroend
!macro progressCopyComplete
!insertmacro progressPercent "${COPY_PROGRESS_END}"
!macroend
!macro progressConfigure
!insertmacro progressPercent "${CONFIGURE_PROGRESS}"
!macroend
!macro progressShortcuts
!insertmacro progressPercent "${SHORTCUTS_PROGRESS}"
!macroend
!macro progressFinalizeInstall
!insertmacro progressPercent "${FINALIZE_PROGRESS}"
!macroend
!macro progressWriteUninstaller
!insertmacro progressPercent "${UNINSTALLER_PROGRESS}"
!macroend
!macro progressFinalize
!insertmacro progressPercent "${COMPLETE_PROGRESS}"
!macroend
!macro progressPhase phase
!insertmacro progressLog "installer:PHASE:${phase}"
!macroend
@ -302,6 +348,7 @@ Var UpdateParentPid
!macroend
!macro customInstall
!insertmacro progressConfigure
!insertmacro progressPhase "Configuring Farm Control"
!insertmacro progressStatus "Registering farmcontrol URI handler..."
DetailPrint "Register farmcontrol URI Handler"
@ -313,6 +360,7 @@ Var UpdateParentPid
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open" "" ""
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open\command" "" '"$FinalInstDir\bin\bun.exe" "$FinalInstDir\bin\deeplink.js" "%1"'
!insertmacro progressShortcuts
!insertmacro progressStatus "Creating shortcuts..."
DetailPrint "Creating shortcuts"
!insertmacro createDesktopShortcut

View File

@ -62,6 +62,20 @@ const isUpdateDismissed = (update) => {
)
}
const formatFullAppVersion = (version, buildNumber) => {
const normalizedVersion = String(version || '')
.trim()
.replace(/^v/i, '')
if (!normalizedVersion) return null
const build = String(buildNumber || '').trim()
const buildSuffix =
!build || build === 'dev' ? 'dev' : build.startsWith('b') ? build : `b${build}`
return `v${normalizedVersion}-${buildSuffix}`
}
const saveDismissedUpdate = (update) => {
if (!update) return
@ -543,13 +557,12 @@ export const AppUpdateProvider = ({ children }) => {
]}
>
<Text>
Farm Control was successfully updated to version{' '}
{completedUpdate?.current?.version || appVersion}
{completedUpdate?.previous?.version &&
completedUpdate.previous.version !== completedUpdate?.current?.version
? ` (previously ${completedUpdate.previous.version})`
: ''}
.
{completedUpdate?.message ||
`Farm Control was successfully updated to version ${
completedUpdate?.current?.version ||
formatFullAppVersion(appVersion, CURRENT_BUILD_NUMBER) ||
`v${appVersion}`
}.`}
</Text>
</Modal>
<Modal

View File

@ -263,6 +263,65 @@ const persistCurrentAppState = async (mainWindow) => {
const stateValueChanged = (previous, next) =>
Boolean(previous) && Boolean(next) && previous !== next
const formatEngineLabel = (engine) => {
const normalized = normalizeEngine(engine)
if (normalized === 'chromium') return 'Chromium'
if (normalized === 'native') return 'Native'
return String(engine || 'Unknown')
}
export const getUpdateInstalledMessage = ({ changes, current } = {}) => {
if (!current) return 'Farm Control was successfully updated.'
const versionChanged = Boolean(changes?.version && current.version)
const branchChanged = Boolean(changes?.branch && current.branch)
const engineChanged = Boolean(changes?.engine && current.engine)
if (!versionChanged && !branchChanged && !engineChanged) {
const version = current.version || getRunningAppVersion()
return version
? `Farm Control was successfully updated to version ${version}.`
: 'Farm Control was successfully updated.'
}
const segments = []
if (versionChanged) {
segments.push({ type: 'version', text: `version ${current.version}` })
}
if (branchChanged) {
segments.push({ type: 'branch', text: `the ${current.branch} branch` })
}
if (engineChanged) {
segments.push({
type: 'engine',
text: `the ${formatEngineLabel(current.engine)} engine`
})
}
if (segments.length === 1) {
const [segment] = segments
if (segment.type === 'engine') {
return `Farm Control was successfully updated to use ${segment.text}.`
}
return `Farm Control was successfully updated to ${segment.text}.`
}
let message = `Farm Control was successfully updated to ${segments[0].text}`
for (const segment of segments.slice(1)) {
if (segment.type === 'branch') {
message += ` on ${segment.text}`
} else if (segment.type === 'engine') {
message += ` using ${segment.text}`
} else {
message += ` and ${segment.text}`
}
}
return `${message}.`
}
let completedUpdateResult = null
export const checkForCompletedUpdate = async (mainWindow) => {
@ -280,18 +339,31 @@ export const checkForCompletedUpdate = async (mainWindow) => {
await setAppSettings({ ...settings, current })
const changes = {
version: stateValueChanged(previous.version, current.version),
branch: stateValueChanged(previous.branch, current.branch),
engine: stateValueChanged(
normalizeEngine(previous.engine),
normalizeEngine(current.engine)
)
}
const updated =
Boolean(previous) &&
(stateValueChanged(previous.version, current.version) ||
stateValueChanged(previous.branch, current.branch) ||
stateValueChanged(
normalizeEngine(previous.engine),
normalizeEngine(current.engine)
))
(changes.version || changes.branch || changes.engine)
const duplicates = checkForDuplicateInstallations()
completedUpdateResult = { updated, previous, current, duplicates }
completedUpdateResult = {
updated,
previous,
current,
changes,
message: updated
? getUpdateInstalledMessage({ changes, current })
: null,
duplicates
}
} catch (error) {
console.warn('[app-update] Failed to check for a completed update.', error)
completedUpdateResult = {

View File

@ -4,7 +4,9 @@ import os from 'os'
import path from 'path'
const PE_MZ_HEADER = Buffer.from([0x4d, 0x5a]) // "MZ"
const COPY_PROGRESS_PERCENT = 75
const COPY_PROGRESS_START = 5
const COPY_PROGRESS_END = 90
const COPY_PROGRESS_RANGE = COPY_PROGRESS_END - COPY_PROGRESS_START
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
@ -54,8 +56,11 @@ const parseWindowsInstallerProgress = (output) => {
copiedBytes += fileBytes
if (copyTotalBytes > 0) {
percent = Math.min(
COPY_PROGRESS_PERCENT,
Math.round((copiedBytes / copyTotalBytes) * COPY_PROGRESS_PERCENT)
COPY_PROGRESS_END,
COPY_PROGRESS_START +
Math.round(
(copiedBytes / copyTotalBytes) * COPY_PROGRESS_RANGE
)
)
}
}