update.js 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. const byId = id => document.getElementById(id)
  2. let closed = false
  3. let localError = '', previousPhase = ''
  4. const sizeLabel = value => value < 1048576 ? `${(value / 1024).toFixed(1)} KB` : `${(value / 1048576).toFixed(1)} MB`
  5. function render(state) {
  6. const busy = ['checking', 'downloading', 'verifying', 'preparing', 'installing'].includes(state.phase)
  7. const ready = state.phase === 'ready'
  8. const automatic = state.installMode === 'restart'
  9. byId('current').textContent = `${state.currentVersion} · ${state.arch}`
  10. byId('latest').textContent = state.latestVersion || (state.phase === 'failed' ? '检查失败' : '待检查')
  11. byId('status').textContent = state.message
  12. byId('error').hidden = !(state.error || localError); byId('error').textContent = state.error || localError
  13. byId('subtitle').textContent = automatic ? '更新当前电脑的 App,下载完成后重启安装。' : '开发运行:从 GitHub 下载,校验后手动安装。'
  14. byId('check').disabled = busy || (automatic && ready) || state.retryable === false
  15. byId('download').disabled = busy || !state.available; byId('download').hidden = ready
  16. byId('download').textContent = automatic ? '下载更新' : state.phase === 'failed' || state.phase === 'cancelled' ? '重试下载' : '下载安装包'
  17. byId('cancel').hidden = automatic ? !state.canCancel : !busy
  18. byId('open').hidden = !ready || automatic; byId('folder').hidden = !ready || automatic
  19. byId('install').hidden = !automatic || (!ready && state.phase !== 'installing')
  20. byId('install').disabled = !ready || busy
  21. byId('install').textContent = state.phase === 'installing' ? '正在重启…' : '重启更新'
  22. byId('progress').hidden = !['downloading', 'verifying', 'preparing'].includes(state.phase)
  23. byId('progress').max = state.total || 1
  24. if (state.total && state.phase === 'downloading') byId('progress').value = state.received || 0
  25. else byId('progress').removeAttribute('value')
  26. byId('bytes').textContent = state.received ? `${sizeLabel(state.received)} / ${sizeLabel(state.total)}` : ''
  27. byId('bytes').hidden = !['downloading', 'verifying', 'preparing'].includes(state.phase)
  28. byId('notes-section').hidden = !state.notes; byId('notes').textContent = state.notes || ''
  29. if (state.phase !== previousPhase && ['download', 'cancel'].includes(document.activeElement?.id)) {
  30. if (ready) byId(automatic ? 'install' : 'open').focus()
  31. else if (!busy) byId(state.available ? 'download' : 'check').focus()
  32. }
  33. previousPhase = state.phase
  34. }
  35. async function action(name) {
  36. if (name !== 'state') localError = ''
  37. try { const state = await window.yaoyaoUpdate[name](); if (state && !closed) render(state) }
  38. catch (error) { if (!closed) { localError = error.message; byId('error').hidden = false; byId('error').textContent = localError } }
  39. }
  40. for (const name of ['check', 'download', 'cancel', 'install', 'open', 'folder', 'release']) byId(name).addEventListener('click', () => void action(name))
  41. const timer = setInterval(() => void action('state'), 500)
  42. window.addEventListener('unload', () => { closed = true; clearInterval(timer) })
  43. document.addEventListener('keydown', event => { if (event.key === 'Escape') window.close() })
  44. // Reopening the window must preserve an active download or staged update.
  45. void (async () => {
  46. try {
  47. const state = await window.yaoyaoUpdate.state()
  48. if (closed) return
  49. render(state)
  50. if (state.phase === 'idle') await action('check')
  51. } catch (error) { if (!closed) { byId('error').hidden = false; byId('error').textContent = error.message } }
  52. })()