Add auto update support

This commit is contained in:
Jonatan Heyman 2023-03-06 01:30:37 +01:00
parent 2c3eacbba0
commit 7ce91016d6
9 changed files with 356 additions and 2 deletions

View File

@ -1,3 +1,12 @@
export const WINDOW_CLOSE_EVENT = "window-close" export const WINDOW_CLOSE_EVENT = "window-close"
export const OPEN_SETTINGS_EVENT = "open-settings" export const OPEN_SETTINGS_EVENT = "open-settings"
export const SETTINGS_CHANGE_EVENT = "settings-change" export const SETTINGS_CHANGE_EVENT = "settings-change"
export const UPDATE_AVAILABLE_EVENT = "update-available"
export const UPDATE_NOT_AVAILABLE_EVENT = "update-not-available"
export const UPDATE_DOWNLOADED = "update-downloaded"
export const UPDATE_ERROR = "update-error"
export const UPDATE_DOWNLOAD_PROGRESS = "update-download-progress"
export const UPDATE_START_DOWNLOAD = "auto-update:startDownload"
export const UPDATE_INSTALL_AND_RESTART = "auto-update:installAndRestart"

View File

@ -0,0 +1,70 @@
import { autoUpdater } from "electron-updater"
import { app, dialog, ipcMain } from "electron"
import {
UPDATE_AVAILABLE_EVENT,
UPDATE_NOT_AVAILABLE_EVENT,
UPDATE_DOWNLOADED,
UPDATE_DOWNLOAD_PROGRESS,
UPDATE_ERROR,
UPDATE_START_DOWNLOAD,
UPDATE_INSTALL_AND_RESTART,
} from '../constants'
// will reference the main window
let window
// configure logging
const log = require('electron-log');
autoUpdater.logger = log
autoUpdater.logger.transports.file.level = "info"
autoUpdater.autoDownload = false
autoUpdater.on('error', (error) => {
window?.webContents.send(UPDATE_ERROR, error == null ? "unknown" : (error.stack || error).toString())
//dialog.showErrorBox('Error: ', error == null ? "unknown" : (error.stack || error).toString())
})
autoUpdater.on('update-available', (info) => {
window?.webContents.send(UPDATE_AVAILABLE_EVENT, {
version: info.version,
releaseDate: info.releaseDate,
currentVersion: app.getVersion(),
})
})
autoUpdater.on('update-not-available', () => {
window?.webContents.send(UPDATE_NOT_AVAILABLE_EVENT)
})
autoUpdater.on('update-downloaded', () => {
window?.webContents.send(UPDATE_DOWNLOADED)
})
autoUpdater.on('download-progress', (info) => {
window?.webContents.send(UPDATE_DOWNLOAD_PROGRESS, {
percent: info.percent,
total: info.total,
transferred: info.transferred,
bytesPerSecond: info.bytesPerSecond,
})
})
// handle messages from Vue components
ipcMain.handle(UPDATE_START_DOWNLOAD, () => {
autoUpdater.downloadUpdate()
})
ipcMain.handle(UPDATE_INSTALL_AND_RESTART, () => {
setImmediate(() => autoUpdater.quitAndInstall())
})
export function checkForUpdates(win) {
window = win
// check for updates
autoUpdater.checkForUpdates()
}

View File

@ -2,12 +2,15 @@ import { app, BrowserWindow, shell, ipcMain, Menu, nativeTheme, globalShortcut }
import { release } from 'node:os' import { release } from 'node:os'
import { join } from 'node:path' import { join } from 'node:path'
import * as jetpack from "fs-jetpack"; import * as jetpack from "fs-jetpack";
import menu from './menu' import menu from './menu'
import { initialContent, initialDevContent } from '../initial-content' import { initialContent, initialDevContent } from '../initial-content'
import { WINDOW_CLOSE_EVENT, SETTINGS_CHANGE_EVENT } from '../constants'; import { WINDOW_CLOSE_EVENT, SETTINGS_CHANGE_EVENT } from '../constants';
import CONFIG from "../config" import CONFIG from "../config"
import { onBeforeInputEvent } from "../keymap" import { onBeforeInputEvent } from "../keymap"
import { isMac } from '../detect-platform'; import { isMac } from '../detect-platform';
import { checkForUpdates } from './auto-update';
// The built directory structure // The built directory structure
// //
@ -134,7 +137,9 @@ async function createWindow() {
}) })
} }
app.whenReady().then(createWindow) app.whenReady().then(createWindow).then(async () => {
checkForUpdates(win)
})
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
win = null win = null

View File

@ -2,7 +2,18 @@ const { contextBridge } = require('electron')
import darkMode from "./theme-mode" import darkMode from "./theme-mode"
import { isMac, isWindows, isLinux } from "../detect-platform" import { isMac, isWindows, isLinux } from "../detect-platform"
import { ipcRenderer } from "electron" import { ipcRenderer } from "electron"
import { WINDOW_CLOSE_EVENT, OPEN_SETTINGS_EVENT, SETTINGS_CHANGE_EVENT } from "../constants" import {
WINDOW_CLOSE_EVENT,
OPEN_SETTINGS_EVENT,
SETTINGS_CHANGE_EVENT,
UPDATE_AVAILABLE_EVENT,
UPDATE_ERROR,
UPDATE_DOWNLOAD_PROGRESS,
UPDATE_NOT_AVAILABLE_EVENT,
UPDATE_START_DOWNLOAD,
UPDATE_INSTALL_AND_RESTART,
UPDATE_DOWNLOADED,
} from "../constants"
import CONFIG from "../config" import CONFIG from "../config"
//contextBridge.exposeInMainWorld("platform", ) //contextBridge.exposeInMainWorld("platform", )
@ -51,6 +62,23 @@ contextBridge.exposeInMainWorld("heynote", {
onSettingsChange(callback) { onSettingsChange(callback) {
ipcRenderer.on(SETTINGS_CHANGE_EVENT, (event, settings) => callback(settings)) ipcRenderer.on(SETTINGS_CHANGE_EVENT, (event, settings) => callback(settings))
}, },
autoUpdate: {
callbacks(callbacks) {
ipcRenderer.on(UPDATE_AVAILABLE_EVENT, (event, info) => callbacks?.updateAvailable(info))
ipcRenderer.on(UPDATE_NOT_AVAILABLE_EVENT, (event) => callbacks?.updateNotAvailable())
ipcRenderer.on(UPDATE_DOWNLOADED, (event) => callbacks?.updateDownloaded())
ipcRenderer.on(UPDATE_ERROR, (event, error) => callbacks?.updateError(error))
ipcRenderer.on(UPDATE_DOWNLOAD_PROGRESS, (event, progress) => callbacks?.updateDownloadProgress(progress))
},
startDownload() {
ipcRenderer.invoke(UPDATE_START_DOWNLOAD)
},
installAndRestart() {
ipcRenderer.invoke(UPDATE_INSTALL_AND_RESTART)
},
},
}) })

120
package-lock.json generated
View File

@ -7,6 +7,9 @@
"": { "": {
"name": "Heynote", "name": "Heynote",
"version": "1.0.0", "version": "1.0.0",
"dependencies": {
"electron-log": "^4.4.8"
},
"devDependencies": { "devDependencies": {
"@codemirror/commands": "^6.1.2", "@codemirror/commands": "^6.1.2",
"@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-cpp": "^6.0.2",
@ -34,6 +37,7 @@
"electron-builder": "^23.6.0", "electron-builder": "^23.6.0",
"electron-builder-notarize": "^1.5.1", "electron-builder-notarize": "^1.5.1",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"electron-updater": "^5.3.0",
"fs-jetpack": "^5.1.0", "fs-jetpack": "^5.1.0",
"prettier": "^2.8.4", "prettier": "^2.8.4",
"sass": "^1.57.1", "sass": "^1.57.1",
@ -1321,6 +1325,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
"integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
"dev": true
},
"node_modules/@types/verror": { "node_modules/@types/verror": {
"version": "1.10.6", "version": "1.10.6",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz",
@ -2831,6 +2841,11 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/electron-log": {
"version": "4.4.8",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz",
"integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA=="
},
"node_modules/electron-notarize": { "node_modules/electron-notarize": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.2.2.tgz", "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.2.2.tgz",
@ -3005,6 +3020,73 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/electron-updater": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-5.3.0.tgz",
"integrity": "sha512-iKEr7yQBcvnQUPnSDYGSWC9t0eF2YbZWeYYYZzYxdl+HiRejXFENjYMnYjoOm2zxyD6Cr2JTHZhp9pqxiXuCOw==",
"dev": true,
"dependencies": {
"@types/semver": "^7.3.6",
"builder-util-runtime": "9.1.1",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.5",
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.5.0",
"semver": "^7.3.5",
"typed-emitter": "^2.1.0"
}
},
"node_modules/electron-updater/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/electron-updater/node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/electron-updater/node_modules/semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/electron-updater/node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true,
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -3943,6 +4025,18 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true "dev": true
}, },
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
"dev": true
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"dev": true
},
"node_modules/lowercase-keys": { "node_modules/lowercase-keys": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
@ -4676,6 +4770,16 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rxjs": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
"integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
"dev": true,
"optional": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safer-buffer": { "node_modules/safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@ -5069,6 +5173,13 @@
"utf8-byte-length": "^1.0.1" "utf8-byte-length": "^1.0.1"
} }
}, },
"node_modules/tslib": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
"dev": true,
"optional": true
},
"node_modules/type-fest": { "node_modules/type-fest": {
"version": "0.13.1", "version": "0.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
@ -5082,6 +5193,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
"dev": true,
"optionalDependencies": {
"rxjs": "*"
}
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.9.4", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",

View File

@ -52,6 +52,7 @@
"electron-builder": "^23.6.0", "electron-builder": "^23.6.0",
"electron-builder-notarize": "^1.5.1", "electron-builder-notarize": "^1.5.1",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"electron-updater": "^5.3.0",
"fs-jetpack": "^5.1.0", "fs-jetpack": "^5.1.0",
"prettier": "^2.8.4", "prettier": "^2.8.4",
"sass": "^1.57.1", "sass": "^1.57.1",
@ -61,5 +62,8 @@
"vite-plugin-electron-renderer": "^0.11.4", "vite-plugin-electron-renderer": "^0.11.4",
"vue": "^3.2.45", "vue": "^3.2.45",
"vue-tsc": "^1.0.16" "vue-tsc": "^1.0.16"
},
"dependencies": {
"electron-log": "^4.4.8"
} }
} }

1
public/icons/update.svg Normal file
View File

@ -0,0 +1 @@
<svg viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M23.995 13.089c.001-.03.005-.059.005-.089a8 8 0 0 0-8-8c-3.814 0-6.998 2.671-7.8 6.242C5.208 12.038 3 14.757 3 18a7 7 0 0 0 7 7h13a5.997 5.997 0 0 0 .995-11.911zM16 21V11" fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" class="stroke-000000"></path><path fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="m12 17 4 4 4-4" class="stroke-000000"></path></svg>

After

Width:  |  Height:  |  Size: 583 B

View File

@ -1,5 +1,7 @@
<script> <script>
import UpdateStatusItem from './UpdateStatusItem.vue'
import { LANGUAGES } from '../editor/languages.js' import { LANGUAGES } from '../editor/languages.js'
const LANGUAGE_MAP = Object.fromEntries(LANGUAGES.map(l => [l.token, l])) const LANGUAGE_MAP = Object.fromEntries(LANGUAGES.map(l => [l.token, l]))
const LANGUAGE_NAMES = Object.fromEntries(LANGUAGES.map(l => [l.token, l.name])) const LANGUAGE_NAMES = Object.fromEntries(LANGUAGES.map(l => [l.token, l.name]))
@ -13,6 +15,10 @@
"systemTheme", "systemTheme",
], ],
components: {
UpdateStatusItem,
},
mounted() { mounted() {
}, },
@ -40,6 +46,7 @@
Ln <span class="num">{{ line }}</span> Ln <span class="num">{{ line }}</span>
Col <span class="num">{{ column }}</span> Col <span class="num">{{ column }}</span>
</div> </div>
<UpdateStatusItem />
<div class="spacer"></div> <div class="spacer"></div>
<div <div
@click="$emit('openLanguageSelector')" @click="$emit('openLanguageSelector')"

View File

@ -0,0 +1,110 @@
<script>
export default {
data() {
return {
updateAvailable: false,
updateDownloaded: false,
downloading: false,
updateProgress: {
percent: 0.0,
transferred: 0.0,
total: 0.0,
bytesPerSecond: 0.0,
}
}
},
mounted() {
window.heynote.autoUpdate.callbacks({
updateAvailable: (info) => {
//console.log("updateAvailable", info)
this.updateAvailable = true
this.currentVersion = info.currentVersion
this.version = info.version
},
updateNotAvailable: () => {
//console.log("updateNotAvailable")
},
updateDownloaded: () => {
//console.log("updateDownloaded")
this.updateDownloaded = true
this.downloading = false
},
updateError: (error) => {
console.log("Update error", error)
this.downloading = false
},
updateDownloadProgress: (progress) => {
//console.log("updateDownloadProgress", progress)
this.downloading = true
this.updateProgress = progress
}
})
},
computed: {
updateAvailableTitle() {
return "Update to version " + this.version + " (current version: " + this.currentVersion + ")"
},
restartTitle() {
return "Click to restart and update Heynote "
},
updateProgressPercent() {
return this.updateProgress.percent.toFixed(0)
}
},
methods: {
startDownload() {
window.heynote.autoUpdate.startDownload()
this.downloading = true
},
installAndRestart() {
window.heynote.autoUpdate.installAndRestart()
},
},
}
</script>
<template>
<div v-if="downloading" class="status-block clickable update-status-block">
<span class="icon-update"></span>
Downloading update {{ updateProgressPercent }}%
</div>
<div v-else-if="updateDownloaded" class="status-block clickable update-status-block" @click="installAndRestart" :title="restartTitle">
<span class="icon-update"></span>
Update &amp; Restart
</div>
<div v-else-if="updateAvailable" class="status-block clickable update-status-block" :title="updateAvailableTitle" @click="startDownload">
<span class="icon-update"></span>
New version available!
</div>
</template>
<style scoped lang="sass">
=dark-mode()
@media (prefers-color-scheme: dark)
@content
.status .status-block.update-status-block
position: relative
padding-left: 30px
.icon-update
display: block
position: absolute
left: 10px
top: 0
width: 16px
height: 22px
+dark-mode
opacity: 0.9
background-size: 16px
background-repeat: no-repeat
background-position: center center
background-image: url("icons/update.svg")
</style>