mirror of
https://github.com/ivabus/gui
synced 2025-06-08 00:00:27 +03:00
Auto updates on the top bar (#488)
* Auto updates on the top bar --------- Co-authored-by: neil molina <neil@neils-MacBook-Pro.local>
This commit is contained in:
parent
3fd5012d8c
commit
544cf09b7c
15 changed files with 236 additions and 102 deletions
|
@ -2,51 +2,60 @@ import { type AppUpdater, autoUpdater } from "electron-updater";
|
||||||
import * as log from "electron-log";
|
import * as log from "electron-log";
|
||||||
import { BrowserWindow } from "electron";
|
import { BrowserWindow } from "electron";
|
||||||
|
|
||||||
|
type AutoUpdateStatus = {
|
||||||
|
status: "up-to-date" | "available" | "ready";
|
||||||
|
version?: string;
|
||||||
|
};
|
||||||
|
|
||||||
autoUpdater.logger = log;
|
autoUpdater.logger = log;
|
||||||
|
|
||||||
let window: BrowserWindow;
|
let window: BrowserWindow;
|
||||||
|
// keep the last status to resend to the window when it's opened becuase the store is destroyed when the window is closed
|
||||||
|
let lastStatus: AutoUpdateStatus = { status: "up-to-date" };
|
||||||
|
|
||||||
export const getUpdater = () => autoUpdater;
|
export const getUpdater = () => autoUpdater;
|
||||||
|
|
||||||
export function checkUpdater(mainWindow: BrowserWindow): AppUpdater {
|
export function checkUpdater(mainWindow: BrowserWindow): AppUpdater {
|
||||||
window = mainWindow;
|
window = mainWindow;
|
||||||
autoUpdater.checkForUpdatesAndNotify();
|
autoUpdater.checkForUpdatesAndNotify();
|
||||||
|
|
||||||
return autoUpdater;
|
return autoUpdater;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendStatusToWindow(text: string, params?: { [key: string]: any }) {
|
// The auto update runs in the background so the window might not be open when the status changes
|
||||||
log.info(text);
|
// When the update store gets created as part of the window it will request the latest status.
|
||||||
window?.webContents.send("message", text, params || {});
|
export function getAutoUpdateStatus() {
|
||||||
|
return lastStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendStatusToWindow(status: AutoUpdateStatus) {
|
||||||
|
lastStatus = status;
|
||||||
|
window?.webContents.send("app-update-status", status);
|
||||||
}
|
}
|
||||||
|
|
||||||
autoUpdater.on("checking-for-update", () => {
|
autoUpdater.on("checking-for-update", () => {
|
||||||
log.info("checking for tea gui update");
|
log.info("checking for tea gui update");
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on("update-available", (info) => {
|
autoUpdater.on("update-available", (info) => {
|
||||||
sendStatusToWindow(
|
sendStatusToWindow({ status: "available" });
|
||||||
`A new tea gui(${info.version}) is being downloaded. Please don't close the app.`,
|
|
||||||
{
|
|
||||||
i18n_key: "notification.gui-downloading",
|
|
||||||
version: info.version
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on("update-not-available", () => {
|
autoUpdater.on("update-not-available", () => {
|
||||||
log.info("no update for tea gui");
|
log.info("no update for tea gui");
|
||||||
|
sendStatusToWindow({ status: "up-to-date" });
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on("error", (err) => {
|
autoUpdater.on("error", (err) => {
|
||||||
log.error("auto update:", err);
|
log.error("auto update:", err);
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on("download-progress", (progressObj) => {
|
autoUpdater.on("download-progress", (progressObj) => {
|
||||||
let log_message = "Download speed: " + progressObj.bytesPerSecond;
|
let log_message = "Download speed: " + progressObj.bytesPerSecond;
|
||||||
log_message = log_message + " - Downloaded " + progressObj.percent + "%";
|
log_message = log_message + " - Downloaded " + progressObj.percent + "%";
|
||||||
log_message = log_message + " (" + progressObj.transferred + "/" + progressObj.total + ")";
|
log_message = log_message + " (" + progressObj.transferred + "/" + progressObj.total + ")";
|
||||||
log.info("tea gui:", log_message);
|
log.info("tea gui:", log_message);
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on("update-downloaded", (info) => {
|
autoUpdater.on("update-downloaded", (info) => {
|
||||||
sendStatusToWindow(`A new tea gui(${info.version}) is available. Relaunch the app to update.`, {
|
sendStatusToWindow({ status: "ready", version: info.version });
|
||||||
i18n_key: "notification.gui-downloaded",
|
|
||||||
version: info.version,
|
|
||||||
action: "relaunch"
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { installPackage, openTerminal, syncPantry } from "./cli";
|
||||||
|
|
||||||
import initializeTeaCli from "./initialize";
|
import initializeTeaCli from "./initialize";
|
||||||
|
|
||||||
import { getUpdater } from "./auto-updater";
|
import { getAutoUpdateStatus, getUpdater } from "./auto-updater";
|
||||||
|
|
||||||
import { loadPackageCache, writePackageCache } from "./package";
|
import { loadPackageCache, writePackageCache } from "./package";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
@ -86,6 +86,7 @@ export default function initializeHandlers() {
|
||||||
|
|
||||||
ipcMain.handle("relaunch", async () => {
|
ipcMain.handle("relaunch", async () => {
|
||||||
try {
|
try {
|
||||||
|
log.info("relaunching app");
|
||||||
const autoUpdater = getUpdater();
|
const autoUpdater = getUpdater();
|
||||||
await autoUpdater.quitAndInstall();
|
await autoUpdater.quitAndInstall();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -192,4 +193,13 @@ export default function initializeHandlers() {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("get-auto-update-status", async () => {
|
||||||
|
try {
|
||||||
|
log.info("getting auto update status");
|
||||||
|
return getAutoUpdateStatus();
|
||||||
|
} catch (error) {
|
||||||
|
log.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { navStore } from "$libs/stores";
|
|
||||||
import { submitLogs } from "@native";
|
|
||||||
import mouseLeaveDelay from "@tea/ui/lib/mouse-leave-delay";
|
|
||||||
|
|
||||||
let submittedMessage = "";
|
|
||||||
const onSubmitLogs = async () => {
|
|
||||||
if (submittedMessage !== "syncing...") {
|
|
||||||
submittedMessage = "syncing...";
|
|
||||||
const msg = await submitLogs();
|
|
||||||
submittedMessage = msg;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const hidePopup = () => {
|
|
||||||
navStore.sideNavOpen.set(false);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<nav class="w-full p-2 text-sm" use:mouseLeaveDelay={2000} on:leave_delay={() => hidePopup()}>
|
|
||||||
<button
|
|
||||||
class="hover:bg-gray outline-gray focus:bg-secondary h-8 w-full p-1 text-left outline-1 transition-all hover:bg-opacity-25 hover:outline"
|
|
||||||
>
|
|
||||||
language
|
|
||||||
</button>
|
|
||||||
<hr />
|
|
||||||
<button
|
|
||||||
class="hover:bg-gray outline-gray focus:bg-secondary h-8 w-full p-1 text-left outline-1 transition-all hover:bg-opacity-25 hover:outline"
|
|
||||||
>
|
|
||||||
docs
|
|
||||||
</button>
|
|
||||||
<hr />
|
|
||||||
<button
|
|
||||||
class="hover:bg-gray outline-gray focus:bg-secondary h-8 w-full p-1 text-left outline-1 transition-all hover:bg-opacity-25 hover:outline"
|
|
||||||
>
|
|
||||||
update tea
|
|
||||||
</button>
|
|
||||||
<hr />
|
|
||||||
<button
|
|
||||||
class="hover:bg-gray outline-gray focus:bg-secondary h-8 w-full p-1 text-left outline-1 transition-all hover:bg-opacity-25 hover:outline"
|
|
||||||
class:animate-pulse={submittedMessage === "syncing..."}
|
|
||||||
on:click={onSubmitLogs}
|
|
||||||
>
|
|
||||||
submit logs
|
|
||||||
</button>
|
|
||||||
{#if submittedMessage}
|
|
||||||
<p class="text-gray mt-2 text-xs">{submittedMessage}</p>
|
|
||||||
{/if}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
hr {
|
|
||||||
border: 1px solid #272626;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { shellOpenExternal, submitLogs } from "@native";
|
||||||
|
import mouseLeaveDelay from "@tea/ui/lib/mouse-leave-delay";
|
||||||
|
import UpdateButton from "./update-button.svelte";
|
||||||
|
import { appUpdateStore } from "$libs/stores";
|
||||||
|
const { updateStatus } = appUpdateStore;
|
||||||
|
|
||||||
|
const hidePopup = () => {
|
||||||
|
isOpen = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const preventDoubleClick = (evt: MouseEvent) => evt.stopPropagation();
|
||||||
|
|
||||||
|
$: isOpen = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="relative"
|
||||||
|
use:mouseLeaveDelay={2000}
|
||||||
|
on:leave_delay={() => hidePopup()}
|
||||||
|
on:dblclick={preventDoubleClick}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="border-gray group flex h-[28px] w-[28px] items-center justify-center rounded-sm border hover:bg-[#e1e1e1]"
|
||||||
|
class:circle-badge={$updateStatus.status === "available" || $updateStatus.status === "ready"}
|
||||||
|
on:click={() => (isOpen = !isOpen)}
|
||||||
|
>
|
||||||
|
<div class="icon-gear text-l text-gray flex group-hover:text-black" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<nav
|
||||||
|
class="menu border-gray absolute w-full border bg-black p-2 text-xs transition-all"
|
||||||
|
class:invisible={!isOpen}
|
||||||
|
class:visible={isOpen}
|
||||||
|
>
|
||||||
|
<!-- TODO: what is this supposed to do? -->
|
||||||
|
<!-- <button
|
||||||
|
class="hover:bg-gray outline-gray focus:bg-secondary h-8 w-full p-1 text-left outline-1 transition-all hover:bg-opacity-25 hover:outline"
|
||||||
|
>
|
||||||
|
language
|
||||||
|
</button>
|
||||||
|
<hr /> -->
|
||||||
|
<button
|
||||||
|
class="hover:bg-gray outline-gray h-7 w-full p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||||
|
on:click={() => shellOpenExternal("https://docs.tea.xyz")}
|
||||||
|
>
|
||||||
|
docs
|
||||||
|
</button>
|
||||||
|
<hr />
|
||||||
|
<UpdateButton />
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
hr {
|
||||||
|
border: 1px solid #272626;
|
||||||
|
margin: 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
top: calc(100% + 4px);
|
||||||
|
left: calc(50% - 80px);
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-badge::after {
|
||||||
|
content: "1";
|
||||||
|
position: absolute;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ff4100;
|
||||||
|
font-size: 10px;
|
||||||
|
top: -7px;
|
||||||
|
right: -7px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,50 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Spinner from "@tea/ui/spinner/spinner.svelte";
|
||||||
|
import { relaunch } from "@native";
|
||||||
|
import { appUpdateStore } from "$libs/stores";
|
||||||
|
|
||||||
|
const { updateStatus } = appUpdateStore;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $updateStatus.status === "up-to-date"}
|
||||||
|
<div
|
||||||
|
class="hover:bg-gray outline-gray flex h-7 w-full items-center justify-between p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||||
|
>
|
||||||
|
<div>up to date</div>
|
||||||
|
<i class="installed-text icon-check-circle-o flex text-[#00ffd0]" />
|
||||||
|
</div>
|
||||||
|
{:else if $updateStatus.status === "available"}
|
||||||
|
<div
|
||||||
|
class="hover:bg-gray outline-gray flex h-7 w-full items-center justify-between p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||||
|
>
|
||||||
|
<div>fetching update</div>
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
{:else if $updateStatus.status === "ready"}
|
||||||
|
<button
|
||||||
|
class="hover:bg-gray outline-gray flex h-7 w-full items-center justify-between p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||||
|
on:click={relaunch}
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="circle-badge mr-2">1</div>
|
||||||
|
<div>update</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-sm bg-white px-2 text-[10px] leading-[12px] text-black">
|
||||||
|
v{$updateStatus.version}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.circle-badge {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
background: #ff4100;
|
||||||
|
font-size: 10px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,13 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { shellOpenExternal, submitLogs } from "@native";
|
import { shellOpenExternal, submitLogs } from "@native";
|
||||||
import { navStore } from "$libs/stores";
|
|
||||||
import LoginButton from "./login-button.svelte";
|
import LoginButton from "./login-button.svelte";
|
||||||
import ToolTip from "@tea/ui/tool-tip/tool-tip.svelte";
|
import ToolTip from "@tea/ui/tool-tip/tool-tip.svelte";
|
||||||
const { sideNavOpen } = navStore;
|
import SettingsMenu from "$components/settings-menu/settings-menu.svelte";
|
||||||
|
|
||||||
const toggleSideNav = () => {
|
|
||||||
sideNavOpen.update((v) => !v);
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitBugReport = async () => {
|
const submitBugReport = async () => {
|
||||||
const logId = await submitLogs();
|
const logId = await submitLogs();
|
||||||
|
@ -20,7 +15,6 @@
|
||||||
|
|
||||||
<div class="mr-1 flex h-full items-center justify-end gap-2 p-2">
|
<div class="mr-1 flex h-full items-center justify-end gap-2 p-2">
|
||||||
<button
|
<button
|
||||||
|
|
||||||
class="border-gray group flex h-[28px] w-[28px] items-center justify-center rounded-sm border hover:bg-[#e1e1e1]"
|
class="border-gray group flex h-[28px] w-[28px] items-center justify-center rounded-sm border hover:bg-[#e1e1e1]"
|
||||||
on:click={() => submitBugReport()}
|
on:click={() => submitBugReport()}
|
||||||
on:dblclick={preventDoubleClick}
|
on:dblclick={preventDoubleClick}
|
||||||
|
@ -32,13 +26,6 @@
|
||||||
</div>
|
</div>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</button>
|
</button>
|
||||||
<!-- Add this back when dropdown is ready -->
|
<SettingsMenu />
|
||||||
<!-- <button
|
|
||||||
class="border-gray group flex h-[28px] w-[28px] items-center justify-center rounded-sm border hover:bg-[#e1e1e1]"
|
|
||||||
on:click={toggleSideNav}
|
|
||||||
on:dblclick={preventDoubleClick}
|
|
||||||
>
|
|
||||||
<div class="icon-gear text-l text-gray flex group-hover:text-black" />
|
|
||||||
</button> -->
|
|
||||||
<LoginButton />
|
<LoginButton />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,7 +12,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Package, Review, AirtablePost, Bottle } from "@tea/ui/types";
|
import type { Package, Review, AirtablePost, Bottle } from "@tea/ui/types";
|
||||||
import { type GUIPackage, type DeviceAuth, type Session, AuthStatus, type Packages } from "./types";
|
import {
|
||||||
|
type GUIPackage,
|
||||||
|
type DeviceAuth,
|
||||||
|
type Session,
|
||||||
|
AuthStatus,
|
||||||
|
type Packages,
|
||||||
|
type AutoUpdateStatus
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
import * as mock from "./native-mock";
|
import * as mock from "./native-mock";
|
||||||
import { PackageStates, type InstalledPackage } from "./types";
|
import { PackageStates, type InstalledPackage } from "./types";
|
||||||
|
@ -191,8 +198,8 @@ export const openTerminal = (cmd: string) => {
|
||||||
|
|
||||||
export const shellOpenExternal = (link: string) => shell.openExternal(link);
|
export const shellOpenExternal = (link: string) => shell.openExternal(link);
|
||||||
|
|
||||||
export const listenToChannel = (channel: string, callback: (msg: string, ...args: any) => void) => {
|
export const listenToChannel = (channel: string, callback: (data: any) => void) => {
|
||||||
ipcRenderer.on(channel, (_: any, message: string, ...args: any[]) => callback(message, ...args));
|
ipcRenderer.on(channel, (_: any, data: any) => callback(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const relaunch = () => ipcRenderer.invoke("relaunch");
|
export const relaunch = () => ipcRenderer.invoke("relaunch");
|
||||||
|
@ -270,3 +277,12 @@ export const cacheImageURL = async (url: string): Promise<string | undefined> =>
|
||||||
log.error("Failed to cache image:", error);
|
log.error("Failed to cache image:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAutoUpdateStatus = async (): Promise<AutoUpdateStatus> => {
|
||||||
|
try {
|
||||||
|
return await ipcRenderer.invoke("get-auto-update-status");
|
||||||
|
} catch (error) {
|
||||||
|
log.error(error);
|
||||||
|
return { status: "up-to-date" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* * make cors work with api.tea.xyz/v1
|
* * make cors work with api.tea.xyz/v1
|
||||||
*/
|
*/
|
||||||
import type { Package, Review, AirtablePost, Bottle } from "@tea/ui/types";
|
import type { Package, Review, AirtablePost, Bottle } from "@tea/ui/types";
|
||||||
import type { GUIPackage, Course, Category, Session, Packages } from "./types";
|
import type { GUIPackage, Course, Category, Session, Packages, AutoUpdateStatus } from "./types";
|
||||||
import { PackageStates } from "./types";
|
import { PackageStates } from "./types";
|
||||||
import { loremIpsum } from "lorem-ipsum";
|
import { loremIpsum } from "lorem-ipsum";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
@ -383,3 +383,7 @@ export const topbarDoubleClick = async () => {
|
||||||
export const cacheImageURL = async (url: string): Promise<string | undefined> => {
|
export const cacheImageURL = async (url: string): Promise<string | undefined> => {
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAutoUpdateStatus = async (): Promise<AutoUpdateStatus> => {
|
||||||
|
return { status: "up-to-date" };
|
||||||
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ import initAuthStore from "./stores/auth";
|
||||||
import initNavStore from "./stores/nav";
|
import initNavStore from "./stores/nav";
|
||||||
import initPackagesStore from "./stores/pkgs";
|
import initPackagesStore from "./stores/pkgs";
|
||||||
import initNotificationStore from "./stores/notifications";
|
import initNotificationStore from "./stores/notifications";
|
||||||
|
import initAppUpdateStore from "./stores/update";
|
||||||
|
|
||||||
export const featuredPackages = writable<Package[]>([]);
|
export const featuredPackages = writable<Package[]>([]);
|
||||||
|
|
||||||
|
@ -135,3 +136,5 @@ export const authStore = initAuthStore();
|
||||||
export const navStore = initNavStore();
|
export const navStore = initNavStore();
|
||||||
|
|
||||||
export const notificationStore = initNotificationStore();
|
export const notificationStore = initNotificationStore();
|
||||||
|
|
||||||
|
export const appUpdateStore = initAppUpdateStore();
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { goto } from "$app/navigation";
|
||||||
const log = window.require("electron-log");
|
const log = window.require("electron-log");
|
||||||
|
|
||||||
export default function initNavStore() {
|
export default function initNavStore() {
|
||||||
const sideNavOpen = writable<boolean>(false);
|
|
||||||
const historyStore = writable<string[]>(["/"]);
|
const historyStore = writable<string[]>(["/"]);
|
||||||
const showWelcome = writable<boolean>(false);
|
const showWelcome = writable<boolean>(false);
|
||||||
|
|
||||||
|
@ -23,7 +22,6 @@ export default function initNavStore() {
|
||||||
return {
|
return {
|
||||||
showWelcome,
|
showWelcome,
|
||||||
historyStore,
|
historyStore,
|
||||||
sideNavOpen,
|
|
||||||
prevPath: prevPathStore,
|
prevPath: prevPathStore,
|
||||||
nextPath: nextPathStore,
|
nextPath: nextPathStore,
|
||||||
next: () => {
|
next: () => {
|
||||||
|
|
|
@ -14,7 +14,9 @@ export default function initNotificationStore() {
|
||||||
update((notifications) => notifications.filter((n) => n.id != id));
|
update((notifications) => notifications.filter((n) => n.id != id));
|
||||||
};
|
};
|
||||||
|
|
||||||
listenToChannel("message", (message: string, params: { [key: string]: string }) => {
|
listenToChannel("message", (data: any) => {
|
||||||
|
const { message, params }: { message: string; params: { [key: string]: string } } = data;
|
||||||
|
|
||||||
update((value) => {
|
update((value) => {
|
||||||
const newNotification: Notification = {
|
const newNotification: Notification = {
|
||||||
id: nanoid(4),
|
id: nanoid(4),
|
||||||
|
|
21
modules/desktop/src/libs/stores/update.ts
Normal file
21
modules/desktop/src/libs/stores/update.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import type { AutoUpdateStatus } from "$libs/types";
|
||||||
|
import { getAutoUpdateStatus, listenToChannel } from "@native";
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export default function initAppUpdateStore() {
|
||||||
|
const updateStatus = writable<AutoUpdateStatus>({ status: "up-to-date" });
|
||||||
|
|
||||||
|
getAutoUpdateStatus().then((status: AutoUpdateStatus) => {
|
||||||
|
updateStatus.update(() => status);
|
||||||
|
});
|
||||||
|
|
||||||
|
listenToChannel("app-update-status", (status: AutoUpdateStatus) => {
|
||||||
|
if (status.status) {
|
||||||
|
updateStatus.update(() => status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateStatus
|
||||||
|
};
|
||||||
|
}
|
|
@ -74,3 +74,8 @@ export enum SideMenuOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InstalledPackage = Required<Pick<GUIPackage, "full_name" | "installed_versions">>;
|
export type InstalledPackage = Required<Pick<GUIPackage, "full_name" | "installed_versions">>;
|
||||||
|
|
||||||
|
export type AutoUpdateStatus = {
|
||||||
|
status: "up-to-date" | "available" | "ready";
|
||||||
|
version?: string;
|
||||||
|
};
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import { navigating } from "$app/stores";
|
import { navigating } from "$app/stores";
|
||||||
import { afterNavigate } from "$app/navigation";
|
import { afterNavigate } from "$app/navigation";
|
||||||
import TopBar from "$components/top-bar/top-bar.svelte";
|
import TopBar from "$components/top-bar/top-bar.svelte";
|
||||||
import PopoutMenu from "$components/popout-menu/popout-menu.svelte";
|
|
||||||
import { navStore, packagesStore, searchStore } from "$libs/stores";
|
import { navStore, packagesStore, searchStore } from "$libs/stores";
|
||||||
import { listenToChannel } from "@native";
|
import { listenToChannel } from "@native";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
|
@ -16,7 +15,7 @@
|
||||||
|
|
||||||
let view: HTMLElement;
|
let view: HTMLElement;
|
||||||
|
|
||||||
const { sideNavOpen, setNewPath } = navStore;
|
const { setNewPath } = navStore;
|
||||||
const { searching } = searchStore;
|
const { searching } = searchStore;
|
||||||
|
|
||||||
$: if ($navigating) view.scrollTop = 0;
|
$: if ($navigating) view.scrollTop = 0;
|
||||||
|
@ -68,11 +67,6 @@
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if $sideNavOpen}
|
|
||||||
<aside class="border-gray fixed z-50 rounded-sm border bg-black transition-all">
|
|
||||||
<PopoutMenu />
|
|
||||||
</aside>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#main-layout {
|
#main-layout {
|
||||||
|
|
8
modules/ui/src/spinner/spinner.svelte
Normal file
8
modules/ui/src/spinner/spinner.svelte
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<svg class="-ml-1 h-5 w-5 animate-spin text-white" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||||
|
<path
|
||||||
|
class="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 356 B |
Loading…
Reference in a new issue