Skip to content

Commit

Permalink
Show toast when language is unavailable offline
Browse files Browse the repository at this point in the history
  • Loading branch information
microbit-robert committed Apr 26, 2024
1 parent f2f0207 commit c89afcd
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 8 deletions.
13 changes: 11 additions & 2 deletions src/language-server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ import {
} from "vscode-languageserver-protocol";
import { retryAsyncLoad } from "../common/chunk-util";
import { microPythonConfig } from "../micropython/micropython";
import { isErrorDueToDispose, OfflineError } from "./error-util";
import {
isErrorDueToDispose,
OfflineError,
showOfflineLanguageToast,
} from "./error-util";
import { fallbackLocale } from "../settings/settings";
import { CreateToastFnReturn } from "@chakra-ui/react";

/**
* Create a URI for a source document under the default root of file:///src/.
Expand Down Expand Up @@ -59,7 +64,8 @@ export class LanguageServerClient extends EventEmitter {
constructor(
public connection: MessageConnection,
public locale: string,
public rootUri: string
public rootUri: string,
private toast: CreateToastFnReturn
) {
super();
}
Expand Down Expand Up @@ -180,7 +186,9 @@ export class LanguageServerClient extends EventEmitter {
return false;
}
if (!navigator.onLine) {
showOfflineLanguageToast(this.toast);
// Fallback to the precached locale if user is offline.
// Currently unused as we are precaching all pyright-locale files.
this.locale = fallbackLocale;
this.initializePromise = undefined;
this.initialize();
Expand All @@ -202,6 +210,7 @@ export class LanguageServerClient extends EventEmitter {
});
} catch (err) {
if (err instanceof OfflineError) {
showOfflineLanguageToast(this.toast);
typeshed = await import(
`../micropython/${branch}/typeshed.${fallbackLocale}.json`
);
Expand Down
25 changes: 25 additions & 0 deletions src/language-server/error-util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CreateToastFnReturn } from "@chakra-ui/react";
import { ConnectionError, ErrorCodes, ResponseError } from "vscode-jsonrpc";

// The language server gets disposed/recreated which can cause errors for
Expand All @@ -8,3 +9,27 @@ export const isErrorDueToDispose = (e: unknown): boolean =>
e instanceof ConnectionError;

export class OfflineError extends Error {}

export const offlineToastTextIds = {
titleId: "offline-language-toast-title",
descriptionId: "offline-language-toast-description",
};

export const showOfflineLanguageToast = (toast: CreateToastFnReturn): void => {
const id = "offline-language-toast";
if (!toast.isActive(id)) {
toast({
id,
// We can't use intl inside the TranslationProvider component.
// Fallback to hardcoded English.
title: "Language unavailable offline",
description:
"Falling back to English. Please reload the page when you are back online to use the app in your language.",
status: "info",
duration: 5_000,
isClosable: true,
position: "top",
variant: "toast",
});
}
};
6 changes: 4 additions & 2 deletions src/language-server/language-server-hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
trackFsChanges,
} from "./client-fs";
import { pyright } from "./pyright";
import { useToast } from "@chakra-ui/react";

const LanguageServerClientContext = createContext<
LanguageServerClient | undefined
Expand All @@ -40,11 +41,12 @@ export const LanguageServerClientProvider = ({
const [clientState, setClientState] = useState<
LanguageServerClient | undefined
>(undefined);
const toast = useToast();
useEffect(() => {
let listener: FsChangesListener | undefined;
let ignore = false;
const initAsync = async () => {
const client = await pyright(languageId);
const client = await pyright(languageId, toast);
if (client) {
listener = trackFsChanges(client, fs);
if (!ignore) {
Expand All @@ -60,7 +62,7 @@ export const LanguageServerClientProvider = ({
ignore = true;
// We don't dispose the client here as it's cached for reuse.
};
}, [fs, languageId]);
}, [fs, languageId, toast]);
return (
<LanguageServerClientContext.Provider value={clientState}>
{children}
Expand Down
11 changes: 9 additions & 2 deletions src/language-server/pyright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "vscode-jsonrpc/browser";
import { baseUrl } from "../base";
import { createUri, LanguageServerClient } from "./client";
import { CreateToastFnReturn } from "@chakra-ui/react";

// This is modified by bin/update-pyright.sh
const workerScriptName = "pyright-main-46e9f54371eb3b42b37c.worker.js";
Expand All @@ -29,7 +30,8 @@ let cache:
* These are recreated when the language changes.
*/
export const pyright = async (
language: string
language: string,
toast: CreateToastFnReturn
): Promise<LanguageServerClient | undefined> => {
// For jsdom.
if (!window.Worker) {
Expand Down Expand Up @@ -91,7 +93,12 @@ export const pyright = async (
});
connection.listen();

const client = new LanguageServerClient(connection, language, createUri(""));
const client = new LanguageServerClient(
connection,
language,
createUri(""),
toast
);
// Must assign before any async step so we reuse or dispose this client
// if another call to pyright is made (language change or React 18 dev mode
// in practice).
Expand Down
10 changes: 8 additions & 2 deletions src/messages/TranslationProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { fallbackLocale, useSettings } from "../settings/settings";
import { IntlProvider, MessageFormatElement } from "react-intl";
import { ReactNode, useEffect, useState } from "react";
import { retryAsyncLoad } from "../common/chunk-util";
import { OfflineError } from "../language-server/error-util";
import {
OfflineError,
showOfflineLanguageToast,
} from "../language-server/error-util";
import { useToast } from "@chakra-ui/react";

async function loadLocaleData(locale: string) {
switch (locale) {
Expand Down Expand Up @@ -47,6 +51,7 @@ interface TranslationProviderProps {
*/
const TranslationProvider = ({ children }: TranslationProviderProps) => {
const [{ languageId }] = useSettings();
const toast = useToast();
// If the messages are for a different language (or missing) then reload them
const [messages, setMessages] = useState<Messages | undefined>();
useEffect(() => {
Expand All @@ -55,14 +60,15 @@ const TranslationProvider = ({ children }: TranslationProviderProps) => {
setMessages(await retryAsyncLoad(() => loadLocaleData(languageId)));
} catch (err) {
if (err instanceof OfflineError) {
showOfflineLanguageToast(toast);
setMessages(await loadLocaleData(fallbackLocale));
} else {
throw err;
}
}
};
load();
}, [languageId]);
}, [languageId, toast]);
return messages ? (
<IntlProvider locale={languageId} defaultLocale="en" messages={messages}>
{children}
Expand Down

0 comments on commit c89afcd

Please sign in to comment.