diff --git a/src/main/updater.ts b/src/main/updater.ts
index ae8bd65..0b80391 100644
--- a/src/main/updater.ts
+++ b/src/main/updater.ts
@@ -77,5 +77,5 @@ function openUpdater(update: UpdateInfo) {
updaterWindow = null;
});
- loadView(updaterWindow, "updater.html");
+ loadView(updaterWindow, "updater/index.html");
}
diff --git a/static/views/about.html b/static/views/about.html
index 933e31a..5a49f35 100644
--- a/static/views/about.html
+++ b/static/views/about.html
@@ -1,7 +1,7 @@
About Vesktop
-
+
-
-
-
-
- An update is available!
-
-
-
-
- Current version:
- New version:
-
- Release Notes
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/static/views/updater/index.html b/static/views/updater/index.html
new file mode 100644
index 0000000..f582d68
--- /dev/null
+++ b/static/views/updater/index.html
@@ -0,0 +1,61 @@
+
+ Vesktop Updater
+
+
+
+
+
+
+
+
+ An update is available!
+
+
+
+
+ Current version:
+ New version:
+
+ Release Notes
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/views/updater/script.js b/static/views/updater/script.js
new file mode 100644
index 0000000..2af79bc
--- /dev/null
+++ b/static/views/updater/script.js
@@ -0,0 +1,69 @@
+const { update, version: currentVersion } = await VesktopUpdaterNative.getData();
+
+document.getElementById("current-version").textContent = currentVersion;
+document.getElementById("new-version").textContent = update.version;
+document.getElementById("release-notes").innerHTML = update.releaseNotes
+ .map(
+ ({ version, note: html }) => `
+
+ Version ${version}
+ ${html.replace(/<\/?h([1-3])/g, (m, level) => m.replace(level, Number(level) + 3))}
+
+ `
+ )
+ .join("\n");
+
+document.querySelectorAll("a").forEach(a => {
+ a.target = "_blank";
+});
+
+// remove useless headings
+document.querySelectorAll("h3, h4, h5, h6").forEach(h => {
+ if (h.textContent.trim().toLowerCase() === "what's changed") {
+ h.remove();
+ }
+});
+
+/** @type {HTMLDialogElement} */
+const updateDialog = document.getElementById("update-dialog");
+/** @type {HTMLDialogElement} */
+const installingDialog = document.getElementById("installing-dialog");
+/** @type {HTMLProgressElement} */
+const downloadProgress = document.getElementById("download-progress");
+/** @type {HTMLElement} */
+const errorText = document.getElementById("error");
+
+document.getElementById("update-button").addEventListener("click", () => {
+ downloadProgress.value = 0;
+ errorText.textContent = "";
+
+ if (navigator.platform.startsWith("Linux")) {
+ document.getElementById("linux-note").classList.remove("hidden");
+ }
+
+ updateDialog.showModal();
+
+ VesktopUpdaterNative.installUpdate().then(() => {
+ downloadProgress.value = 100;
+ updateDialog.closedBy = "any";
+
+ installingDialog.showModal();
+ updateDialog.classList.add("hidden");
+ });
+});
+
+document.getElementById("later-button").addEventListener("click", () => VesktopUpdaterNative.snoozeUpdate());
+document.getElementById("ignore-button").addEventListener("click", () => {
+ const confirmed = confirm(
+ "Are you sure you want to ignore this update? You will not be notified about this update again. Updates are important for security and stability."
+ );
+ if (confirmed) VesktopUpdaterNative.ignoreUpdate();
+});
+
+VesktopUpdaterNative.onProgress(percent => (downloadProgress.value = percent));
+VesktopUpdaterNative.onError(message => {
+ updateDialog.closedBy = "any";
+ errorText.textContent = `An error occurred while downloading the update: ${message}`;
+ installingDialog.close();
+ updateDialog.classList.remove("hidden");
+});
diff --git a/static/views/updater/style.css b/static/views/updater/style.css
new file mode 100644
index 0000000..97db89f
--- /dev/null
+++ b/static/views/updater/style.css
@@ -0,0 +1,147 @@
+html,
+body {
+ height: 100%;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ padding: 2em;
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+
+ header,
+ footer {
+ flex: 0 0 auto;
+ }
+
+ main {
+ flex: 1 1 0%;
+ min-height: 0;
+ overflow: auto;
+ }
+
+ footer {
+ margin-top: 1em;
+ }
+}
+
+
+#versions {
+ display: inline-grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 0.5em;
+
+ .label {
+ white-space: nowrap;
+ }
+
+ .value {
+ text-align: left;
+ }
+
+ #current-version {
+ color: #fc8f8a;
+ }
+
+ #new-version {
+ color: #71c07f;
+ }
+}
+
+#release-notes {
+ display: grid;
+ gap: 1em;
+}
+
+#buttons {
+ display: flex;
+ gap: 0.5em;
+ justify-content: end;
+}
+
+button {
+ cursor: pointer;
+ padding: 0.5rem 1rem;
+ font-size: 1.2em;
+ color: var(--fg);
+ border: none;
+ border-radius: 3px;
+ font-weight: bold;
+ transition: filter 0.2 ease-in-out;
+}
+
+button:hover,
+button:active {
+ filter: brightness(0.9);
+}
+
+.green {
+ background-color: #248046;
+}
+
+.grey {
+ background-color: rgba(151, 151, 159, 0.12);
+}
+
+.hidden {
+ display: none;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin: 0 0 0.5em 0;
+}
+
+h1 {
+ text-align: center;
+}
+
+h2 {
+ margin-bottom: 1em;
+}
+
+dialog {
+ width: 80%;
+ padding: 2em;
+}
+
+progress {
+ width: 100%;
+ height: 1.5em;
+ margin-top: 1em;
+}
+
+#error {
+ color: red;
+ font-weight: bold;
+}
+
+.spinner-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 2em;
+}
+
+.spinner {
+ width: 48px;
+ height: 48px;
+ border: 5px solid var(--fg);
+ border-bottom-color: transparent;
+ border-radius: 50%;
+ display: inline-block;
+ box-sizing: border-box;
+ animation: rotation 1s linear infinite;
+}
+
+@keyframes rotation {
+ to {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file