diff --git a/src/App.svelte b/src/App.svelte index a72773be4..c26423a73 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -9,10 +9,12 @@ import PageContentView from './views/PageContentView.svelte'; import { compatibility, + hasSeenAppVersionRedirectDialog, isCompatibilityWarningDialogOpen, } from './script/stores/uiStore'; import IncompatiblePlatformView from './views/IncompatiblePlatformView.svelte'; import CompatibilityWarningDialog from './components/CompatibilityWarningDialog.svelte'; + import AppVersionRedirectDialog from './components/AppVersionRedirectDialog.svelte'; import Router from './router/Router.svelte'; import ControlBar from './components/control-bar/ControlBar.svelte'; import { t } from './i18n'; @@ -31,8 +33,17 @@ connectionDialogState, } from './script/stores/connectDialogStore'; import { isLoading } from 'svelte-i18n'; + import { fetchBrowserInfo } from './script/utils/api'; + import { get } from 'svelte/store'; + + let isPotentiallyNextGenUser: boolean = false; + onMount(async () => { + if (!get(hasSeenAppVersionRedirectDialog)) { + const { country } = await fetchBrowserInfo(); + const nextGenAvailableCountries = ['GB', 'JE', 'IM', 'GG']; + isPotentiallyNextGenUser = !!country && nextGenAvailableCountries.includes(country); + } - onMount(() => { const { bluetooth, usb } = $compatibility; // Value must switch from false to true after mount to trigger dialog transition isCompatibilityWarningDialogOpen.set(!bluetooth && !usb); @@ -65,6 +76,9 @@ {#if $consent} {/if} + {#if $consent && !$isCompatibilityWarningDialogOpen && isPotentiallyNextGenUser} + + {/if}
diff --git a/src/components/AppVersionRedirectDialog.svelte b/src/components/AppVersionRedirectDialog.svelte new file mode 100644 index 000000000..40c1edd07 --- /dev/null +++ b/src/components/AppVersionRedirectDialog.svelte @@ -0,0 +1,52 @@ + + + + + + + {$t('popup.appVersionRedirect.header')} + + +
+

{$t('popup.appVersionRedirect.explain')}

+
+ {$t('popup.appVersionRedirect.button.redirect')} + {$t('popup.appVersionRedirect.button.stay')} +
+

{$t('popup.appVersionRedirect.uk')}

+
+
+
diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index 2c51abac8..df752bacd 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -284,6 +284,12 @@ "popup.outdatedmicrobit.button.update": "Update now", "popup.outdatedmicrobit.button.update.mkcd": "Open MakeCode", + "popup.appVersionRedirect.header": "Are you a UK* primary school teacher?", + "popup.appVersionRedirect.explain": "If you are a UK* primary school teacher, you will be redirected to the version of this tool designed for the UK's BBC micro:bit playground survey.", + "popup.appVersionRedirect.button.redirect": "I'm a UK primary school teacher", + "popup.appVersionRedirect.button.stay": "I'm not a UK primary school teacher", + "popup.appVersionRedirect.uk": "*includes crown dependencies Jersey, Guernsey and the Isle of Man", + "arrowIconRight.altText": "arrow pointing right", "arrowIconDown.altText": "arrow pointing down", diff --git a/src/script/stores/uiStore.ts b/src/script/stores/uiStore.ts index 8491bc21e..8ed378c60 100644 --- a/src/script/stores/uiStore.ts +++ b/src/script/stores/uiStore.ts @@ -14,6 +14,7 @@ import MBSpecs from '../microbit-interfacing/MBSpecs'; import { gestures } from './Stores'; import { HexOrigin } from '../../StaticConfiguration'; import { DeviceRequestStates } from '../microbit-interfacing/MicrobitConnection'; +import { persistantWritable } from './storeUtil'; import { logError, logEvent } from '../utils/logging'; // TODO: Rename? Split up further? @@ -42,6 +43,11 @@ if (compatibilityResult.bluetooth) { export const isCompatibilityWarningDialogOpen = writable(false); +export const hasSeenAppVersionRedirectDialog = persistantWritable( + 'hasSeenAppVersionRedirectDialog', + false, +); + export enum ModelView { TILE, STACK, diff --git a/src/script/utils/api.ts b/src/script/utils/api.ts new file mode 100644 index 000000000..45f1054bc --- /dev/null +++ b/src/script/utils/api.ts @@ -0,0 +1,36 @@ +import { logError } from './logging'; + +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ +interface BrowserInfo { + country?: string; + region?: string; +} + +const isBrowserInfo = (v: unknown): v is BrowserInfo => { + return typeof v === 'object' && v !== null; +}; + +/** + * Best effort attempt to fetch browser info. + * On error it returns empty browser info. + */ +export const fetchBrowserInfo = async (): Promise => { + try { + // Note this API is not available if you're running locally without configuring API_PROXY in .env + const response = await fetch('/api/v1/browser/info'); + if (!response.ok) { + return {}; + } + const json = await response.json(); + if (isBrowserInfo(json)) { + return json; + } + } catch (e) { + logError('Failed to fetch browser info', e); + } + return {}; +}; diff --git a/vite.config.ts b/vite.config.ts index b8d756e67..48f3dca48 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,48 +4,66 @@ * * SPDX-License-Identifier: MIT */ -import { defineConfig } from 'vite'; +import { defineConfig, loadEnv } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import WindiCSS from 'vite-plugin-windicss'; import { preprocessMeltUI, sequence } from '@melt-ui/pp'; import { sveltePreprocess } from 'svelte-preprocess/dist/autoProcess'; import Icons from 'unplugin-icons/vite'; -export default defineConfig({ - base: process.env.BASE_URL ?? '/', - plugins: [ - svelte({ - preprocess: sequence([sveltePreprocess({ typescript: true }), preprocessMeltUI()]), +export default defineConfig(({ mode }) => { + const commonEnv = loadEnv(mode, process.cwd(), ''); - onwarn(warning, defaultHandler) { - if (warning.code.includes('a11y')) return; // Ignores the a11y warnings when compiling. This does not apply to the editor, see comment at bottom for vscode instructions + return { + base: process.env.BASE_URL ?? '/', + plugins: [ + svelte({ + preprocess: sequence([ + sveltePreprocess({ typescript: true }), + preprocessMeltUI(), + ]), - // handle all other warnings normally - defaultHandler!(warning); + onwarn(warning, defaultHandler) { + if (warning.code.includes('a11y')) return; // Ignores the a11y warnings when compiling. This does not apply to the editor, see comment at bottom for vscode instructions + + // handle all other warnings normally + defaultHandler!(warning); + }, + }), + WindiCSS(), + Icons({ compiler: 'svelte' }), + ], + define: { + 'import.meta.env.VITE_APP_VERSION': JSON.stringify(process.env.npm_package_version), + }, + build: { + target: 'es2017', + rollupOptions: { + input: 'index.html', }, - }), - WindiCSS(), - Icons({ compiler: 'svelte' }), - ], - define: { - 'import.meta.env.VITE_APP_VERSION': JSON.stringify(process.env.npm_package_version), - }, - build: { - target: 'es2017', - rollupOptions: { - input: 'index.html', }, - }, - test: { - globals: true, - setupFiles: ['./src/setup_tests.ts'], - poolOptions: { - threads: { - // threads disabled for now due to https://github.com/vitest-dev/vitest/issues/1982 - singleThread: true, + server: commonEnv.API_PROXY + ? { + port: 5172, + proxy: { + '/api/v1': { + target: commonEnv.API_PROXY, + changeOrigin: true, + }, + }, + } + : undefined, + test: { + globals: true, + setupFiles: ['./src/setup_tests.ts'], + poolOptions: { + threads: { + // threads disabled for now due to https://github.com/vitest-dev/vitest/issues/1982 + singleThread: true, + }, }, }, - }, + }; }); /**