implement update prompt, clean service worker handling

This commit is contained in:
marlon-erler
2026-09-24 22:07:36 +02:00
parent ecf72a7318
commit a63488d62a
10 changed files with 625 additions and 930 deletions
+251 -662
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -15,7 +15,6 @@
<link rel="stylesheet" href="index.css" /> <link rel="stylesheet" href="index.css" />
<link rel="manifest" href="manifest.json" /> <link rel="manifest" href="manifest.json" />
<link rel="icon" href="icon.png" /> <link rel="icon" href="icon.png" />
<script src="support.js" defer></script>
<script src="index.js" defer></script> <script src="index.js" defer></script>
</head> </head>
+59 -2
View File
@@ -353,6 +353,7 @@
settingsButton: "Settings", settingsButton: "Settings",
manageStorageButton: "Manage storage", manageStorageButton: "Manage storage",
transferDataButton: "Transfer or export data", transferDataButton: "Transfer or export data",
updateButton: (version) => `Update to ${version}`,
scrollToChatButton: "Chats", scrollToChatButton: "Chats",
/// ///
backToOverviewAudioLabel: "go back to overview", backToOverviewAudioLabel: "go back to overview",
@@ -596,6 +597,7 @@
settingsButton: "Einstellungen", settingsButton: "Einstellungen",
manageStorageButton: "Daten verwalten", manageStorageButton: "Daten verwalten",
transferDataButton: "Daten \xFCbertragen", transferDataButton: "Daten \xFCbertragen",
updateButton: (version) => `Aktualisieren: ${version}`,
scrollToChatButton: "Chats", scrollToChatButton: "Chats",
backToOverviewAudioLabel: "zur\xFCck zur \xFCbersicht", backToOverviewAudioLabel: "zur\xFCck zur \xFCbersicht",
chatsHeadline: "Chats", chatsHeadline: "Chats",
@@ -820,6 +822,7 @@
settingsButton: "Ajustes", settingsButton: "Ajustes",
manageStorageButton: "Gestionar almacenamiento", manageStorageButton: "Gestionar almacenamiento",
transferDataButton: "Enviar o exportar archivos", transferDataButton: "Enviar o exportar archivos",
updateButton: (version) => `Actualizar a ${version}`,
scrollToChatButton: "Chats", scrollToChatButton: "Chats",
backToOverviewAudioLabel: "volver al resumen", backToOverviewAudioLabel: "volver al resumen",
chatsHeadline: "Chats", chatsHeadline: "Chats",
@@ -1314,7 +1317,12 @@
this.connectionModel = connectionModel2; this.connectionModel = connectionModel2;
this.chatListModel = chatListModel2; this.chatListModel = chatListModel2;
this.fileTransferModel = fileTransferModel2; this.fileTransferModel = fileTransferModel2;
this.BUILD = "Build 26.09.24.D"; this.swRegistration = void 0;
this.version = new State("");
this.latestVersion = new State("");
this.noUpdateAvailable = createProxyState([this.latestVersion], () => {
return this.latestVersion.value == "" || this.latestVersion.value == this.version.value;
});
// CONTEXT // CONTEXT
this.contextStack = /* @__PURE__ */ new Map(); this.contextStack = /* @__PURE__ */ new Map();
this.closeContext = (contextId, fromHistoryEvent = false) => { this.closeContext = (contextId, fromHistoryEvent = false) => {
@@ -1356,12 +1364,46 @@
}; };
// DRAG & DROP // DRAG & DROP
this.draggedObject = new State(void 0); this.draggedObject = new State(void 0);
// OFFLINE MODE & UPDATE
this.configureServiceWorker = async () => {
if ("serviceWorker" in navigator) {
try {
this.swRegistration = await navigator.serviceWorker.register(
"sw.js",
{
scope: "/"
}
);
if (this.swRegistration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Could not install service worker: ${error}`);
}
}
fetch("/version").then(async (response) => {
this.version.value = await response.text();
});
fetch("/latestVersion").then(async (response) => {
this.latestVersion.value = (await response.text()).replace("\n", "");
console.log("LATEST", this.latestVersion.value);
});
};
this.update = async () => {
if (!this.swRegistration) return;
await this.swRegistration.update();
window.location.reload();
};
this.translations = allTranslations[settingsModel2.language] || allTranslations.en; this.translations = allTranslations[settingsModel2.language] || allTranslations.en;
document.body.addEventListener("keydown", this.handleKeyDown); document.body.addEventListener("keydown", this.handleKeyDown);
window.onpopstate = () => { window.onpopstate = () => {
if (!this.context) return; if (!this.context) return;
this.closeContext(this.context.contextId, true); this.closeContext(this.context.contextId, true);
}; };
this.configureServiceWorker();
this.noUpdateAvailable.subscribe(() => {
console.log(this.noUpdateAvailable.value, this.latestVersion.value, this.version.value);
});
this.startChron(); this.startChron();
this.chronHandlerManager.setHandler( this.chronHandlerManager.setHandler(
"core-view-model", "core-view-model",
@@ -4955,6 +4997,7 @@
// src/View/homePage.tsx // src/View/homePage.tsx
function HomePage(coreViewModel2, storageViewModel2, settingsViewModel2, connectionViewModel2, fileTransferViewModel2, chatListViewModel2) { function HomePage(coreViewModel2, storageViewModel2, settingsViewModel2, connectionViewModel2, fileTransferViewModel2, chatListViewModel2) {
const updateText = createProxyState([coreViewModel2.latestVersion], () => coreViewModel2.translations.homePage.updateButton(coreViewModel2.latestVersion.value));
const overviewSection = /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.overviewHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.homePage.serverAddress), /* @__PURE__ */ createElement( const overviewSection = /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.overviewHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.homePage.serverAddress), /* @__PURE__ */ createElement(
"input", "input",
{ {
@@ -5029,6 +5072,20 @@
storageViewModel2.showStorageModal, storageViewModel2.showStorageModal,
coreViewModel2.translations.homePage.manageStorageButton, coreViewModel2.translations.homePage.manageStorageButton,
"hard_drive" "hard_drive"
), /* @__PURE__ */ createElement(
"button",
{
class: "primary",
"on:click": coreViewModel2.update,
"toggle:hidden": coreViewModel2.noUpdateAvailable
},
/* @__PURE__ */ createElement(
"span",
{
"subscribe:innerText": updateText
}
),
/* @__PURE__ */ createElement("span", { class: "icon" }, "update")
), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, coreViewModel2.translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward"))))); ), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, coreViewModel2.translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")))));
const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
"input", "input",
@@ -7221,7 +7278,7 @@
return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.appName), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile( return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.appName), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile(
"build", "build",
coreViewModel2.translations.settings.version, coreViewModel2.translations.settings.version,
settingsViewModel2.coreViewModel.BUILD settingsViewModel2.coreViewModel.version
))); )));
} }
function SettingsRegionalPane(coreViewModel2, settingsViewModel2) { function SettingsRegionalPane(coreViewModel2, settingsViewModel2) {
-35
View File
@@ -1,35 +0,0 @@
// TABS
document.querySelector("main").addEventListener("scroll", () => {
const links = document.querySelectorAll(".tab-link");
links.forEach((element) => element.removeAttribute("active"));
const pageWidth = document.querySelector("main > article").offsetWidth;
const scroll = document.querySelector("main").scrollLeft;
const currentPage = Math.round(scroll / pageWidth);
if (links[currentPage]) links[currentPage].setAttribute("active", "");
});
// SERVICE WORKER
async function registerServiceWorker() {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register(
"/sw.js",
{
scope: "/",
},
);
if (registration.installing) {
setTimeout(() => window.location.reload(), 500);
} else if (registration.active) {
console.log("Service worker active");
registration.update();
}
} catch (error) {
console.error(`Could not install service worker: ${error}`);
}
}
}
registerServiceWorker();
Vendored
+35 -14
View File
@@ -1,32 +1,53 @@
const CACHE_VERSION = "3"; const VERSION = "2610 Beta 1"
self.addEventListener("fetch", (event) => { function processUrl(event) {
event.respondWith(handleRequest(event)); const url = event.request.url;
const fileName = url.split("/").pop();
return {url, fileName};
}
self.addEventListener("install", () => {
self.skipWaiting();
}); });
async function handleRequest(event) { self.addEventListener("fetch", async (event) => {
const response = await getFromCache(event); console.log(VERSION);
if (response) { const {url, fileName}=processUrl(event);
fetchAndCache(event); // Call this but don't await, allowing the cache to update in the background event.respondWith(handleRequest(event, url, fileName));
return response; });
async function handleRequest(event, url, fileName) {
switch (fileName) {
case "version":
const response = new Response(VERSION);
return response;
case "latestVersion":
return await fetch("/version.txt", {cache: "no-cache"})
} }
return fetchAndCache(event); const response = await getFromCache(event);
if (response) {
fetchAndCache(event, fileName); // Call this but don't await, allowing the cache to update in the background
return response;
}
return fetchAndCache(event, fileName);
} }
async function getFromCache(event) { async function getFromCache(event) {
const request = event.request; const request = event.request;
const cache = await caches.open(CACHE_VERSION); const cache = await caches.open(VERSION);
const response = await cache.match(request); const response = await cache.match(request);
return response; return response;
} }
async function fetchAndCache(event) { async function fetchAndCache(event, fileName) {
const request = event.request; const request = event.request;
const response = await fetch(request, { cache: "no-cache" }); const response = await fetch(request, { cache: "no-cache" });
if (response.status === 200) { if (response.status === 200) {
// Only cache successful responses // Only cache successful responses
const cache = await caches.open(CACHE_VERSION); const cache = await caches.open(VERSION);
cache.put(request, response.clone()); await cache.put(request, response.clone());
} }
return response; return response;
} }
+1
View File
@@ -0,0 +1 @@
2610 Beta 1
+1 -1
View File
@@ -122,7 +122,7 @@ function SettingsInfoPane(
{InfoTile( {InfoTile(
"build", "build",
coreViewModel.translations.settings.version, coreViewModel.translations.settings.version,
settingsViewModel.coreViewModel.BUILD, settingsViewModel.coreViewModel.version,
)} )}
</div> </div>
</div> </div>
+179 -166
View File
@@ -19,185 +19,198 @@ export function HomePage(
chatListViewModel: ChatListViewModel, chatListViewModel: ChatListViewModel,
) { ) {
// sections // sections
const updateText = React.createProxyState([coreViewModel.latestVersion], ()=>coreViewModel.translations.homePage.updateButton(coreViewModel.latestVersion.value));
const overviewSection = ( const overviewSection = (
<div id="overview-section"> <div id="overview-section">
<h2>{coreViewModel.translations.homePage.overviewHeadline}</h2> <h2>{coreViewModel.translations.homePage.overviewHeadline}</h2>
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">cell_tower</span> <span class="icon">cell_tower</span>
<div> <div>
<span> <span>
{coreViewModel.translations.homePage.serverAddress} {coreViewModel.translations.homePage.serverAddress}
</span> </span>
<input <input
list="previous-connection-list" list="previous-connection-list"
placeholder={ placeholder={
coreViewModel.translations.homePage coreViewModel.translations.homePage
.serverAddressPlaceholder .serverAddressPlaceholder
} }
bind:value={connectionViewModel.serverAddressInput} bind:value={connectionViewModel.serverAddressInput}
on:enter={connectionViewModel.connect} on:enter={connectionViewModel.connect}
></input> ></input>
<datalist <datalist
hidden hidden
id="previous-connection-list" id="previous-connection-list"
children:append={[ children:append={[
connectionViewModel.previousAddresses, connectionViewModel.previousAddresses,
StringToOption, StringToOption,
]} ]}
></datalist> ></datalist>
</div> </div>
</label> </label>
<div class="flex-row"> <div class="flex-row">
<button <button
class="danger flex justify-center" class="danger flex justify-center"
aria-label={ aria-label={
coreViewModel.translations.homePage.disconnectAudioLabel coreViewModel.translations.homePage.disconnectAudioLabel
} }
on:click={connectionViewModel.disconnect} on:click={connectionViewModel.disconnect}
toggle:disabled={connectionViewModel.cannotDisonnect} toggle:disabled={connectionViewModel.cannotDisonnect}
> >
<span class="icon">link_off</span> <span class="icon">link_off</span>
</button> </button>
<button <button
class="flex justify-center" class="flex justify-center"
aria-label={ aria-label={
coreViewModel.translations.homePage coreViewModel.translations.homePage
.manageConnectionsAudioLabel .manageConnectionsAudioLabel
} }
on:click={connectionViewModel.showConnectionModal} on:click={connectionViewModel.showConnectionModal}
toggle:disabled={ toggle:disabled={
connectionViewModel.hasNoPreviousConnections connectionViewModel.hasNoPreviousConnections
} }
> >
<span class="icon">history</span> <span class="icon">history</span>
</button> </button>
<button <button
class="primary flex justify-center" class="primary flex justify-center"
aria-label={ aria-label={
coreViewModel.translations.homePage.connectAudioLabel coreViewModel.translations.homePage.connectAudioLabel
} }
on:click={connectionViewModel.connect} on:click={connectionViewModel.connect}
toggle:disabled={connectionViewModel.cannotConnect} toggle:disabled={connectionViewModel.cannotConnect}
> >
<span class="icon">link</span> <span class="icon">link</span>
</button> </button>
</div> </div>
<hr></hr> <hr></hr>
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">account_circle</span> <span class="icon">account_circle</span>
<div> <div>
<span> <span>
{coreViewModel.translations.homePage.yourNameLabel} {coreViewModel.translations.homePage.yourNameLabel}
</span> </span>
<input <input
placeholder={ placeholder={
coreViewModel.translations.homePage coreViewModel.translations.homePage
.yourNamePlaceholder .yourNamePlaceholder
} }
bind:value={settingsViewModel.usernameInput} bind:value={settingsViewModel.usernameInput}
on:enter={settingsViewModel.setName} on:enter={settingsViewModel.setName}
></input> ></input>
</div> </div>
</label> </label>
<div class="flex-row justify-end"> <div class="flex-row justify-end">
<button <button
class="width-50" class="width-50"
on:click={settingsViewModel.setName} on:click={settingsViewModel.setName}
toggle:disabled={settingsViewModel.cannotSetName} toggle:disabled={settingsViewModel.cannotSetName}
aria-label={ aria-label={
coreViewModel.translations.homePage coreViewModel.translations.homePage
.setNameButtonAudioLabel .setNameButtonAudioLabel
} }
> >
{coreViewModel.translations.general.setButton} {coreViewModel.translations.general.setButton}
<span class="icon">check</span> <span class="icon">check</span>
</button> </button>
</div> </div>
<hr></hr> <hr></hr>
{HomePageButton( {HomePageButton(
settingsViewModel.showSettingsModal, settingsViewModel.showSettingsModal,
coreViewModel.translations.homePage.settingsButton, coreViewModel.translations.homePage.settingsButton,
"settings", "settings",
)} )}
{HomePageButton( {HomePageButton(
fileTransferViewModel.showDirectionSelectionModal, fileTransferViewModel.showDirectionSelectionModal,
coreViewModel.translations.homePage.transferDataButton, coreViewModel.translations.homePage.transferDataButton,
"sync_alt", "sync_alt",
)} )}
{HomePageButton( {HomePageButton(
storageViewModel.showStorageModal, storageViewModel.showStorageModal,
coreViewModel.translations.homePage.manageStorageButton, coreViewModel.translations.homePage.manageStorageButton,
"hard_drive", "hard_drive",
)} )}
<div class="mobile-only"> <button
<hr></hr> class="primary"
on:click={coreViewModel.update}
toggle:hidden={coreViewModel.noUpdateAvailable}
>
<span
subscribe:innerText={updateText}
></span>
<span class="icon">update</span>
</button>
<div class="flex-row justify-end"> <div class="mobile-only">
<button class="ghost width-50" on:click={scrollToChat}> <hr></hr>
{coreViewModel.translations.homePage.scrollToChatButton}
<span class="icon">arrow_forward</span>
</button>
</div>
</div>
</div>
);
const chatSection = ( <div class="flex-row justify-end">
<div id="chat-section"> <button class="ghost width-50" on:click={scrollToChat}>
<h2>{coreViewModel.translations.homePage.chatsHeadline}</h2> {coreViewModel.translations.homePage.scrollToChatButton}
<span class="icon">arrow_forward</span>
</button>
</div>
</div>
</div>
);
<div class="flex-row width-input"> const chatSection = (
<input <div id="chat-section">
placeholder={ <h2>{coreViewModel.translations.homePage.chatsHeadline}</h2>
coreViewModel.translations.homePage.addChatPlaceholder
}
aria-label={
coreViewModel.translations.homePage.addChatAudioLabel
}
bind:value={chatListViewModel.newChatPrimaryChannel}
on:enter={chatListViewModel.createChat}
></input>
<button
class="primary"
aria-label={
coreViewModel.translations.homePage.addChatButton
}
on:click={chatListViewModel.createChat}
toggle:disabled={chatListViewModel.cannotCreateChat}
>
<span class="icon">add</span>
</button>
</div>
<div <div class="flex-row width-input">
id="chat-grid" <input
children:append={[ placeholder={
chatListViewModel.chatViewModels, coreViewModel.translations.homePage.addChatPlaceholder
ChatViewModelToChatEntry, }
]} aria-label={
></div> coreViewModel.translations.homePage.addChatAudioLabel
</div> }
); bind:value={chatListViewModel.newChatPrimaryChannel}
on:enter={chatListViewModel.createChat}
></input>
<button
class="primary"
aria-label={
coreViewModel.translations.homePage.addChatButton
}
on:click={chatListViewModel.createChat}
toggle:disabled={chatListViewModel.cannotCreateChat}
>
<span class="icon">add</span>
</button>
</div>
// methods <div
function scrollToChat() { id="chat-grid"
chatSection.scrollIntoView(); children:append={[
} chatListViewModel.chatViewModels,
ChatViewModelToChatEntry,
]}
></div>
</div>
);
// final // methods
return ( function scrollToChat() {
<article id="home-page"> chatSection.scrollIntoView();
<div> }
{overviewSection}
{chatSection} // final
</div> return (
</article> <article id="home-page">
<div>
{overviewSection}
{chatSection}
</div>
</article>
); );
} }
+6
View File
@@ -73,6 +73,8 @@ const englishTranslations = {
manageStorageButton: "Manage storage", manageStorageButton: "Manage storage",
transferDataButton: "Transfer or export data", transferDataButton: "Transfer or export data",
updateButton: (version: string) => `Update to ${version}`,
scrollToChatButton: "Chats", scrollToChatButton: "Chats",
/// ///
@@ -412,6 +414,8 @@ export const allTranslations: {
manageStorageButton: "Daten verwalten", manageStorageButton: "Daten verwalten",
transferDataButton: "Daten übertragen", transferDataButton: "Daten übertragen",
updateButton: (version: string) => `Aktualisieren: ${version}`,
scrollToChatButton: "Chats", scrollToChatButton: "Chats",
backToOverviewAudioLabel: "zurück zur übersicht", backToOverviewAudioLabel: "zurück zur übersicht",
@@ -717,6 +721,8 @@ export const allTranslations: {
manageStorageButton: "Gestionar almacenamiento", manageStorageButton: "Gestionar almacenamiento",
transferDataButton: "Enviar o exportar archivos", transferDataButton: "Enviar o exportar archivos",
updateButton: (version: string) => `Actualizar a ${version}`,
scrollToChatButton: "Chats", scrollToChatButton: "Chats",
backToOverviewAudioLabel: "volver al resumen", backToOverviewAudioLabel: "volver al resumen",
+93 -49
View File
@@ -9,7 +9,13 @@ import ConnectionModel from "../../Model/Global/connectionModel";
import ChatListModel from "../../Model/Chat/chatListModel"; import ChatListModel from "../../Model/Chat/chatListModel";
export default class CoreViewModel { export default class CoreViewModel {
readonly BUILD = "Build 26.09.24.D"; swRegistration: ServiceWorkerRegistration|undefined = undefined;
version: React.State<string> = new React.State("");
latestVersion: React.State<string> = new React.State("");
noUpdateAvailable: React.State<boolean> = React.createProxyState([this.latestVersion], () => {
return (this.latestVersion.value == "" || this.latestVersion.value == this.version.value)
})
translations: Translations; translations: Translations;
@@ -84,34 +90,72 @@ export default class CoreViewModel {
// DRAG & DROP // DRAG & DROP
draggedObject: React.State<any> = new React.State<any>(undefined); draggedObject: React.State<any> = new React.State<any>(undefined);
// OFFLINE MODE & UPDATE
configureServiceWorker = async (): Promise<void> => {
if ("serviceWorker" in navigator) {
try {
this.swRegistration = await navigator.serviceWorker.register(
"sw.js",
{
scope: "/",
},
);
if (this.swRegistration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Could not install service worker: ${error}`);
}
}
fetch("/version").then(async response => {
this.version.value = await response.text();
});
fetch("/latestVersion").then(async response => {
this.latestVersion.value = (await response.text()).replace("\n", "");
console.log("LATEST", this.latestVersion.value);
})
}
update = async (): Promise<void> => {
if (!this.swRegistration) return;
await this.swRegistration.update();
window.location.reload();
}
// init // init
constructor( constructor(
public readonly storageModel: StorageModel, public readonly storageModel: StorageModel,
public readonly settingsModel: SettingsModel, public readonly settingsModel: SettingsModel,
public readonly connectionModel: ConnectionModel, public readonly connectionModel: ConnectionModel,
public readonly chatListModel: ChatListModel, public readonly chatListModel: ChatListModel,
public readonly fileTransferModel: FileTransferModel, public readonly fileTransferModel: FileTransferModel,
) { ) {
this.translations = this.translations =
allTranslations[settingsModel.language] || allTranslations.en; allTranslations[settingsModel.language] || allTranslations.en;
document.body.addEventListener("keydown", this.handleKeyDown); document.body.addEventListener("keydown", this.handleKeyDown);
window.onpopstate = () => { window.onpopstate = () => {
if (!this.context) return; if (!this.context) return;
this.closeContext(this.context.contextId, true); this.closeContext(this.context.contextId, true);
}; };
this.startChron(); this.configureServiceWorker();
this.chronHandlerManager.setHandler( this.noUpdateAvailable.subscribe(() => {
"core-view-model", console.log(this.noUpdateAvailable.value, this.latestVersion.value, this.version.value);
this.handleChron, })
);
this.startChron();
this.chronHandlerManager.setHandler(
"core-view-model",
this.handleChron,
);
} }
// util // util
static checkIsKeystroke(e: KeyboardEvent): boolean { static checkIsKeystroke(e: KeyboardEvent): boolean {
return (e.metaKey || e.altKey) && e.ctrlKey; return (e.metaKey || e.altKey) && e.ctrlKey;
} }
} }
@@ -120,19 +164,19 @@ export class Context {
keystrokes = new Map<string, () => void>(); keystrokes = new Map<string, () => void>();
handleKeystroke = (e: KeyboardEvent): boolean => { handleKeystroke = (e: KeyboardEvent): boolean => {
const fn: (() => void) | undefined = this.keystrokes.get( const fn: (() => void) | undefined = this.keystrokes.get(
e.key.toLowerCase(), e.key.toLowerCase(),
); );
if (!fn) return false; if (!fn) return false;
fn(); fn();
return true; return true;
}; };
close = (): void => {}; close = (): void => {};
handleContextClose = (fromHistoryEvent: boolean): void => {}; handleContextClose = (fromHistoryEvent: boolean): void => {};
registerKeyStroke = (key: string, fn: () => void): void => { registerKeyStroke = (key: string, fn: () => void): void => {
this.keystrokes.set(key, fn); this.keystrokes.set(key, fn);
}; };
constructor(public contextDebugDescription: string) {} constructor(public contextDebugDescription: string) {}
@@ -143,45 +187,45 @@ export class ContextHost<T> extends Context {
currentContext = new React.State<Context | undefined>(undefined); currentContext = new React.State<Context | undefined>(undefined);
get isOpen(): boolean { get isOpen(): boolean {
return false; return false;
} }
get contextSelection(): T | undefined { get contextSelection(): T | undefined {
return undefined; return undefined;
} }
registerContext = (key: T, context: Context): void => { registerContext = (key: T, context: Context): void => {
this.contexts.set(key, context); this.contexts.set(key, context);
}; };
closeCurrentContext = (): void => { closeCurrentContext = (): void => {
if (this.currentContext.value) { if (this.currentContext.value) {
this.coreViewModel.closeContext( this.coreViewModel.closeContext(
this.currentContext.value.contextId, this.currentContext.value.contextId,
); );
} }
this.currentContext.value = undefined; this.currentContext.value = undefined;
}; };
updateContexts = (): void => { updateContexts = (): void => {
if (this.isOpen == false) return; if (this.isOpen == false) return;
const selection = this.contextSelection; const selection = this.contextSelection;
if (!selection) return; if (!selection) return;
const selectedContext: Context | undefined = const selectedContext: Context | undefined =
this.contexts.get(selection); this.contexts.get(selection);
if (!selectedContext) return; if (!selectedContext) return;
if (selectedContext != this.currentContext.value) { if (selectedContext != this.currentContext.value) {
this.closeCurrentContext(); this.closeCurrentContext();
} }
this.coreViewModel.context = selectedContext; this.coreViewModel.context = selectedContext;
this.currentContext.value = selectedContext; this.currentContext.value = selectedContext;
}; };
constructor( constructor(
contextDebugDescription: string, contextDebugDescription: string,
public coreViewModel: CoreViewModel, public coreViewModel: CoreViewModel,
) { ) {
super(contextDebugDescription); super(contextDebugDescription);
} }
} }