diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 86119b95a..70ad78cfc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -22,6 +22,7 @@ jobs:
PRODUCTION_CLOUDFRONT_DISTRIBUTION_ID: E2ELTBTA2OFPY2
STAGING_CLOUDFRONT_DISTRIBUTION_ID: E2ELTBTA2OFPY2
REVIEW_CLOUDFRONT_DISTRIBUTION_ID: E3267W09ZJHQG9
+ REACT_APP_FOUNDATION_BUILD: ${{ github.repository_owner == 'microbit-foundation' }}
steps:
# Note: This workflow disables deployment steps and micro:bit branding installation on forks.
@@ -35,7 +36,7 @@ jobs:
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - run: npm install --no-save @microbit-foundation/python-editor-v3-microbit@0.1.0-dev.198 @microbit-foundation/website-deploy-aws@0.3.0 @microbit-foundation/website-deploy-aws-config@0.7.1 @microbit-foundation/circleci-npm-package-versioner@1
+ - run: npm install --no-save @microbit-foundation/python-editor-v3-microbit@0.2.0-dev.18 @microbit-foundation/website-deploy-aws@0.3.0 @microbit-foundation/website-deploy-aws-config@0.7.1 @microbit-foundation/circleci-npm-package-versioner@1
if: github.repository_owner == 'microbit-foundation'
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/lang/ui.ca.json b/lang/ui.ca.json
index a94acbb42..d53287236 100644
--- a/lang/ui.ca.json
+++ b/lang/ui.ca.json
@@ -195,6 +195,10 @@
"defaultMessage": "Alguna cosa ha anat malament. Baixa el teu fitxer hexadecimal per mantenir-lo segur i, a continuació, actualitza la pàgina per tornar-la a carregar.",
"description": "Text displayed when content fails to load"
},
+ "cookies-action": {
+ "defaultMessage": "Cookies",
+ "description": "Action to show dialog to choose website cookie preferences"
+ },
"copied": {
"defaultMessage": "Copiat",
"description": "Text shown after copy to clipboard"
diff --git a/lang/ui.en.json b/lang/ui.en.json
index be2d13265..248c9d3b6 100644
--- a/lang/ui.en.json
+++ b/lang/ui.en.json
@@ -195,6 +195,10 @@
"defaultMessage": "Something went wrong. Download your hex file for safe keeping, then refresh the page to reload.",
"description": "Text displayed when content fails to load"
},
+ "cookies-action": {
+ "defaultMessage": "Cookies",
+ "description": "Action to show dialog to choose website cookie preferences"
+ },
"copied": {
"defaultMessage": "Copied",
"description": "Text shown after copy to clipboard"
diff --git a/lang/ui.es-es.json b/lang/ui.es-es.json
index e2f8c2907..0c702f70a 100644
--- a/lang/ui.es-es.json
+++ b/lang/ui.es-es.json
@@ -195,6 +195,10 @@
"defaultMessage": "Algo salió mal. Por seguridad, descarga tu archivo HEX y actualiza la página para recargar.",
"description": "Text displayed when content fails to load"
},
+ "cookies-action": {
+ "defaultMessage": "Cookies",
+ "description": "Action to show dialog to choose website cookie preferences"
+ },
"copied": {
"defaultMessage": "Copiado",
"description": "Text shown after copy to clipboard"
diff --git a/lang/ui.fr.json b/lang/ui.fr.json
index c0eb1d903..543c7abd9 100644
--- a/lang/ui.fr.json
+++ b/lang/ui.fr.json
@@ -195,6 +195,10 @@
"defaultMessage": "Une erreur est survenue. Téléchargez votre fichier hex pour ne pas le perdre, puis actualisez la page.",
"description": "Text displayed when content fails to load"
},
+ "cookies-action": {
+ "defaultMessage": "Cookies",
+ "description": "Action to show dialog to choose website cookie preferences"
+ },
"copied": {
"defaultMessage": "Copié",
"description": "Text shown after copy to clipboard"
diff --git a/lang/ui.ja.json b/lang/ui.ja.json
index acb27089f..774f88ef4 100644
--- a/lang/ui.ja.json
+++ b/lang/ui.ja.json
@@ -195,6 +195,10 @@
"defaultMessage": "問題が発生しました。hex ファイルをダウンロードして、ページをリロードしてください。",
"description": "Text displayed when content fails to load"
},
+ "cookies-action": {
+ "defaultMessage": "Cookies",
+ "description": "Action to show dialog to choose website cookie preferences"
+ },
"copied": {
"defaultMessage": "コピーしました",
"description": "Text shown after copy to clipboard"
diff --git a/lang/ui.ko.json b/lang/ui.ko.json
index cd45f9838..0f148421f 100644
--- a/lang/ui.ko.json
+++ b/lang/ui.ko.json
@@ -195,6 +195,10 @@
"defaultMessage": "오류가 발생했습니다. hex 파일을 다운로드해 작업물을 보호하고 새로 고침으로 페이지를 다시 불러오세요.",
"description": "Text displayed when content fails to load"
},
+ "cookies-action": {
+ "defaultMessage": "Cookies",
+ "description": "Action to show dialog to choose website cookie preferences"
+ },
"copied": {
"defaultMessage": "복사됨",
"description": "Text shown after copy to clipboard"
diff --git a/lang/ui.nl.json b/lang/ui.nl.json
index 61f866d70..52555630e 100644
--- a/lang/ui.nl.json
+++ b/lang/ui.nl.json
@@ -195,6 +195,10 @@
"defaultMessage": "Er is iets fout gegaan. Download je hexadecimale bestand voor een veilige bewaring, ververs daarna de pagina om te herladen.",
"description": "Text displayed when content fails to load"
},
+ "cookies-action": {
+ "defaultMessage": "Cookies",
+ "description": "Action to show dialog to choose website cookie preferences"
+ },
"copied": {
"defaultMessage": "Gekopieerd",
"description": "Text shown after copy to clipboard"
diff --git a/lang/ui.zh-cn.json b/lang/ui.zh-cn.json
index 43cc76358..8bd9b2c88 100644
--- a/lang/ui.zh-cn.json
+++ b/lang/ui.zh-cn.json
@@ -195,6 +195,10 @@
"defaultMessage": "出错了。下载您的 hex 文件以便安全保存,然后刷新页面来重新加载。",
"description": "Text displayed when content fails to load"
},
+ "cookies-action": {
+ "defaultMessage": "Cookies",
+ "description": "Action to show dialog to choose website cookie preferences"
+ },
"copied": {
"defaultMessage": "已复制",
"description": "Text shown after copy to clipboard"
diff --git a/lang/ui.zh-tw.json b/lang/ui.zh-tw.json
index 840ab3e6d..8cb18f409 100644
--- a/lang/ui.zh-tw.json
+++ b/lang/ui.zh-tw.json
@@ -195,6 +195,10 @@
"defaultMessage": "發生錯誤。下載您的 HEX 檔案以便安全儲存,然後重新整理頁面來重新載入。",
"description": "Text displayed when content fails to load"
},
+ "cookies-action": {
+ "defaultMessage": "Cookies",
+ "description": "Action to show dialog to choose website cookie preferences"
+ },
"copied": {
"defaultMessage": "複製的",
"description": "Text shown after copy to clipboard"
diff --git a/package-lock.json b/package-lock.json
index 7fc54aff8..c0d33676b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -49,7 +49,7 @@
"mobile-drag-drop": "^2.3.0-rc.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
- "react-icons": "^4.2.0",
+ "react-icons": "^4.8.0",
"react-intl": "^5.20.10",
"vscode-jsonrpc": "^6.0.0",
"vscode-languageserver-protocol": "^3.16.0",
@@ -19743,9 +19743,9 @@
}
},
"node_modules/react-icons": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.2.0.tgz",
- "integrity": "sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==",
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
+ "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
"peerDependencies": {
"react": "*"
}
@@ -38516,9 +38516,9 @@
}
},
"react-icons": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.2.0.tgz",
- "integrity": "sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==",
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
+ "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
"requires": {}
},
"react-intl": {
diff --git a/package.json b/package.json
index cf8450668..d1b8b5f3e 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
"mobile-drag-drop": "^2.3.0-rc.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
- "react-icons": "^4.2.0",
+ "react-icons": "^4.8.0",
"react-intl": "^5.20.10",
"vscode-jsonrpc": "^6.0.0",
"vscode-languageserver-protocol": "^3.16.0",
diff --git a/public/index.html b/public/index.html
index 24c52dba1..00ae47ae9 100644
--- a/public/index.html
+++ b/public/index.html
@@ -20,25 +20,15 @@
name="twitter:description"
content="A Python Editor for the BBC micro:bit, built by the Micro:bit Educational Foundation and the global Python Community."
/>
- <% if (process.env.REACT_APP_GA_MEASUREMENT_ID &&
- (process.env.REACT_APP_STAGE === 'PRODUCTION' || process.env.REACT_APP_STAGE
- === "STAGING")) { %>
-
+ <% if (process.env.REACT_APP_FOUNDATION_BUILD === 'true') { %>
+
+
<% } %>
diff --git a/src/App.tsx b/src/App.tsx
index a025e1d98..1e2187d5a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -62,6 +62,7 @@ const App = () => {
});
const deployment = useDeployment();
+ const { ConsentProvider } = deployment.compliance;
return (
<>
@@ -79,11 +80,13 @@ const App = () => {
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/deployment/default/index.tsx b/src/deployment/default/index.tsx
index 884a5df09..352736847 100644
--- a/src/deployment/default/index.tsx
+++ b/src/deployment/default/index.tsx
@@ -3,13 +3,31 @@
*
* SPDX-License-Identifier: MIT
*/
-import { DeploymentConfig } from "..";
+import { createContext } from "react";
+import { CookieConsent, DeploymentConfig } from "..";
import { NullLogging } from "./logging";
import theme from "./theme";
+const stubConsentValue: CookieConsent = {
+ analytics: false,
+ functional: true,
+};
+const stubConsentContext = createContext(
+ stubConsentValue
+);
+
const defaultDeployment: DeploymentConfig = {
chakraTheme: theme,
logging: new NullLogging(),
+ compliance: {
+ ConsentProvider: ({ children }) => (
+
+ {children}
+
+ ),
+ consentContext: stubConsentContext,
+ manageCookies: undefined,
+ },
};
export default defaultDeployment;
diff --git a/src/deployment/index.ts b/src/deployment/index.ts
index 61ba1edf0..03f8c7c06 100644
--- a/src/deployment/index.ts
+++ b/src/deployment/index.ts
@@ -3,24 +3,36 @@
*
* SPDX-License-Identifier: MIT
*/
-import { ReactNode } from "react";
-import { IconType } from "react-icons/lib";
+import { ReactNode, useContext } from "react";
import { Logging } from "../logging/logging";
// This is configured via a webpack alias, defaulting to ./default
import { default as d } from "theme-package";
export const deployment: DeploymentConfig = d;
+export interface CookieConsent {
+ analytics: boolean;
+ functional: boolean;
+}
+
export interface DeploymentConfig {
squareLogo?: ReactNode;
horizontalLogo?: ReactNode;
- Compliance?: ({
- zIndex,
- externalLinkIcon,
- }: {
- zIndex: number;
- externalLinkIcon: IconType;
- }) => JSX.Element;
+ compliance: {
+ /**
+ * A provider that will be used to wrap the app UI.
+ */
+ ConsentProvider: (props: { children: ReactNode }) => JSX.Element;
+ /**
+ * Context that will be used to read the current consent value.
+ * The provider is not used directly.
+ */
+ consentContext: React.Context;
+ /**
+ * Optional hook for the user to revisit cookie settings.
+ */
+ manageCookies: (() => void) | undefined;
+ };
chakraTheme: any;
@@ -35,3 +47,8 @@ export interface DeploymentConfig {
export const useDeployment = (): DeploymentConfig => {
return deployment;
};
+
+export const useCookieConsent = (): CookieConsent | undefined => {
+ const { compliance } = useDeployment();
+ return useContext(compliance.consentContext);
+};
diff --git a/src/e2e/app.ts b/src/e2e/app.ts
index 5f0212b2b..02ba453df 100644
--- a/src/e2e/app.ts
+++ b/src/e2e/app.ts
@@ -126,10 +126,16 @@ export class App {
value: "1",
url: this.url,
});
- // Don't show compliance notice.
+ // Don't show compliance notice for Foundation builds
await page.setCookie({
name: "MBCC",
- value: "1",
+ value: encodeURIComponent(
+ JSON.stringify({
+ version: 1,
+ analytics: false,
+ functional: true,
+ })
+ ),
url: this.url,
});
diff --git a/src/messages/ui.ca.json b/src/messages/ui.ca.json
index 4144f03bd..6fcf67da5 100644
--- a/src/messages/ui.ca.json
+++ b/src/messages/ui.ca.json
@@ -411,6 +411,12 @@
"value": "Alguna cosa ha anat malament. Baixa el teu fitxer hexadecimal per mantenir-lo segur i, a continuació, actualitza la pàgina per tornar-la a carregar."
}
],
+ "cookies-action": [
+ {
+ "type": 0,
+ "value": "Cookies"
+ }
+ ],
"copied": [
{
"type": 0,
diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json
index f855d5841..311634217 100644
--- a/src/messages/ui.en.json
+++ b/src/messages/ui.en.json
@@ -407,6 +407,12 @@
"value": "Something went wrong. Download your hex file for safe keeping, then refresh the page to reload."
}
],
+ "cookies-action": [
+ {
+ "type": 0,
+ "value": "Cookies"
+ }
+ ],
"copied": [
{
"type": 0,
diff --git a/src/messages/ui.es-es.json b/src/messages/ui.es-es.json
index 8eafbb0b0..1588fa397 100644
--- a/src/messages/ui.es-es.json
+++ b/src/messages/ui.es-es.json
@@ -411,6 +411,12 @@
"value": "Algo salió mal. Por seguridad, descarga tu archivo HEX y actualiza la página para recargar."
}
],
+ "cookies-action": [
+ {
+ "type": 0,
+ "value": "Cookies"
+ }
+ ],
"copied": [
{
"type": 0,
diff --git a/src/messages/ui.fr.json b/src/messages/ui.fr.json
index 9bd5b7145..df5a2e339 100644
--- a/src/messages/ui.fr.json
+++ b/src/messages/ui.fr.json
@@ -407,6 +407,12 @@
"value": "Une erreur est survenue. Téléchargez votre fichier hex pour ne pas le perdre, puis actualisez la page."
}
],
+ "cookies-action": [
+ {
+ "type": 0,
+ "value": "Cookies"
+ }
+ ],
"copied": [
{
"type": 0,
diff --git a/src/messages/ui.ja.json b/src/messages/ui.ja.json
index 1b2138d94..536b66b2a 100644
--- a/src/messages/ui.ja.json
+++ b/src/messages/ui.ja.json
@@ -431,6 +431,12 @@
"value": "問題が発生しました。hex ファイルをダウンロードして、ページをリロードしてください。"
}
],
+ "cookies-action": [
+ {
+ "type": 0,
+ "value": "Cookies"
+ }
+ ],
"copied": [
{
"type": 0,
diff --git a/src/messages/ui.ko.json b/src/messages/ui.ko.json
index 94077d95c..ade0f3fb2 100644
--- a/src/messages/ui.ko.json
+++ b/src/messages/ui.ko.json
@@ -415,6 +415,12 @@
"value": "오류가 발생했습니다. hex 파일을 다운로드해 작업물을 보호하고 새로 고침으로 페이지를 다시 불러오세요."
}
],
+ "cookies-action": [
+ {
+ "type": 0,
+ "value": "Cookies"
+ }
+ ],
"copied": [
{
"type": 0,
diff --git a/src/messages/ui.nl.json b/src/messages/ui.nl.json
index 559feb68f..934206e71 100644
--- a/src/messages/ui.nl.json
+++ b/src/messages/ui.nl.json
@@ -419,6 +419,12 @@
"value": "Er is iets fout gegaan. Download je hexadecimale bestand voor een veilige bewaring, ververs daarna de pagina om te herladen."
}
],
+ "cookies-action": [
+ {
+ "type": 0,
+ "value": "Cookies"
+ }
+ ],
"copied": [
{
"type": 0,
diff --git a/src/messages/ui.zh-cn.json b/src/messages/ui.zh-cn.json
index 82efc1329..b831daba9 100644
--- a/src/messages/ui.zh-cn.json
+++ b/src/messages/ui.zh-cn.json
@@ -407,6 +407,12 @@
"value": "出错了。下载您的 hex 文件以便安全保存,然后刷新页面来重新加载。"
}
],
+ "cookies-action": [
+ {
+ "type": 0,
+ "value": "Cookies"
+ }
+ ],
"copied": [
{
"type": 0,
diff --git a/src/messages/ui.zh-tw.json b/src/messages/ui.zh-tw.json
index 1bca34cca..d622df00c 100644
--- a/src/messages/ui.zh-tw.json
+++ b/src/messages/ui.zh-tw.json
@@ -403,6 +403,12 @@
"value": "發生錯誤。下載您的 HEX 檔案以便安全儲存,然後重新整理頁面來重新載入。"
}
],
+ "cookies-action": [
+ {
+ "type": 0,
+ "value": "Cookies"
+ }
+ ],
"copied": [
{
"type": 0,
diff --git a/src/workbench/HelpMenu.tsx b/src/workbench/HelpMenu.tsx
index d1ce33fc7..521809c88 100644
--- a/src/workbench/HelpMenu.tsx
+++ b/src/workbench/HelpMenu.tsx
@@ -16,6 +16,7 @@ import {
useDisclosure,
} from "@chakra-ui/react";
import { useCallback, useRef } from "react";
+import { MdOutlineCookie } from "react-icons/md";
import {
RiExternalLinkLine,
RiFeedbackLine,
@@ -25,7 +26,7 @@ import {
import { FormattedMessage, useIntl } from "react-intl";
import { useDialogs } from "../common/use-dialogs";
import { zIndexAboveTerminal } from "../common/zIndex";
-import { deployment } from "../deployment";
+import { deployment, useDeployment } from "../deployment";
import AboutDialog from "./AboutDialog/AboutDialog";
import FeedbackForm from "./FeedbackForm";
@@ -49,6 +50,11 @@ const HelpMenu = ({ size, ...props }: HelpMenuProps) => {
/>
));
}, [dialogs]);
+ const { compliance } = useDeployment();
+ const handleCookies = useCallback(() => {
+ // Only called if defined:
+ compliance.manageCookies!();
+ }, [compliance]);
const menuButtonRef = useRef(null);
return (
<>
@@ -106,7 +112,13 @@ const HelpMenu = ({ size, ...props }: HelpMenuProps) => {
)}
-
+ {deployment.compliance.manageCookies && (
+ } onClick={handleCookies}>
+
+
+ )}
+ {(deployment.termsOfUseLink ||
+ deployment.compliance.manageCookies) && }
}
onClick={aboutDialogDisclosure.onOpen}
diff --git a/src/workbench/PreReleaseNotice.tsx b/src/workbench/PreReleaseNotice.tsx
index 61a49b767..116209132 100644
--- a/src/workbench/PreReleaseNotice.tsx
+++ b/src/workbench/PreReleaseNotice.tsx
@@ -8,6 +8,7 @@ import { Flex, HStack, Text } from "@chakra-ui/layout";
import { useCallback, useEffect, useState } from "react";
import { RiFeedbackFill, RiInformationFill } from "react-icons/ri";
import { useStorage } from "../common/use-storage";
+import { useCookieConsent } from "../deployment";
import { flags } from "../flags";
export type ReleaseNoticeState = "info" | "feedback" | "closed";
@@ -39,13 +40,18 @@ export const useReleaseDialogState = (): [
);
const [releaseDialog, setReleaseDialog] =
useState("closed");
- // Show the dialog on start-up once per user.
+ // Show the dialog on start-up once per user once we have cookie consent.
+ const cookieConsent = useCookieConsent();
useEffect(() => {
- if (!flags.noWelcome && storedNotice.version < currentVersion) {
+ if (
+ cookieConsent &&
+ !flags.noWelcome &&
+ storedNotice.version < currentVersion
+ ) {
setReleaseDialog("info");
setStoredNotice({ version: currentVersion });
}
- }, [storedNotice, setStoredNotice, setReleaseDialog]);
+ }, [cookieConsent, storedNotice, setStoredNotice, setReleaseDialog]);
return [releaseDialog, setReleaseDialog];
};
diff --git a/src/workbench/Workbench.tsx b/src/workbench/Workbench.tsx
index e3cc425c8..bd797d132 100644
--- a/src/workbench/Workbench.tsx
+++ b/src/workbench/Workbench.tsx
@@ -6,7 +6,6 @@
import { Box, Flex } from "@chakra-ui/layout";
import { useMediaQuery } from "@chakra-ui/react";
import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
-import { RiExternalLinkLine } from "react-icons/ri";
import { useIntl } from "react-intl";
import {
hideSidebarMediaQuery,
@@ -21,8 +20,6 @@ import {
SplitViewSized,
} from "../common/SplitView";
import { SizedMode } from "../common/SplitView/SplitView";
-import { zIndexAboveDialogs } from "../common/zIndex";
-import { useDeployment } from "../deployment";
import { ConnectionStatus } from "../device/device";
import { useConnectionStatus } from "../device/device-hooks";
import EditorArea from "../editor/EditorArea";
@@ -120,23 +117,9 @@ const Workbench = () => {
)}
);
- const inIframe = () => {
- try {
- return window.self !== window.top;
- } catch (e) {
- return true;
- }
- };
- const deployment = useDeployment();
- const Compliance = deployment.Compliance ?? (() => null);
+
return (
- {!inIframe() && (
-
- )}