update impressum & privacy policy

This commit is contained in:
Jon Eugster
2024-04-24 14:31:29 +02:00
parent a1b1a33a9b
commit eac945e7b5
12 changed files with 216 additions and 76 deletions
+18 -8
View File
@@ -123,16 +123,21 @@ function InputModeButton({setNavOpen, isDropdown}) {
</Button>
}
/** button to toggle iimpressum popup
*
* Note: Do not translate the word "Impressum"! German GDPR needs this.
*/
export function ImpressumButton({setNavOpen, toggleImpressum, isDropdown}) {
const { t } = useTranslation()
return <Button className="btn btn-inverted"
title={t("information, Impressum, privacy policy")} inverted="true" to="" onClick={(ev) => {toggleImpressum(ev); setNavOpen(false)}}>
title={t("Impressum")} inverted="true" to="" onClick={(ev) => {toggleImpressum(ev); setNavOpen(false)}}>
<FontAwesomeIcon icon={faCircleInfo} />
{isDropdown && <>&nbsp;Impressum</>}
{isDropdown && <>&nbsp;{t("Impressum")}</>}
</Button>
}
export function PrivacyButton({setNavOpen, togglePrivacy, isDropdown}) {
const { t } = useTranslation()
return <Button className="btn btn-inverted"
title={t("Privacy Policy")} inverted="true" to="" onClick={(ev) => {togglePrivacy(ev); setNavOpen(false)}}>
<FontAwesomeIcon icon={faCircleInfo} />
{isDropdown && <>&nbsp;{t("Privacy Policy")}</>}
</Button>
}
@@ -204,11 +209,12 @@ function InventoryButton({pageNumber, setPageNumber}) {
}
/** the navigation bar on the welcome page */
export function WelcomeAppBar({pageNumber, setPageNumber, gameInfo, toggleImpressum, toggleEraseMenu, toggleUploadMenu, toggleInfo, togglePreferencesPopup} : {
export function WelcomeAppBar({pageNumber, setPageNumber, gameInfo, toggleImpressum, togglePrivacy, toggleEraseMenu, toggleUploadMenu, toggleInfo, togglePreferencesPopup} : {
pageNumber: number,
setPageNumber: any,
gameInfo: GameInfo,
toggleImpressum: any,
togglePrivacy: any,
toggleEraseMenu: any,
toggleUploadMenu: any,
toggleInfo: any,
@@ -238,16 +244,18 @@ export function WelcomeAppBar({pageNumber, setPageNumber, gameInfo, toggleImpres
<DownloadButton setNavOpen={setNavOpen} gameId={gameId} gameProgress={gameProgress}/>
<UploadButton setNavOpen={setNavOpen} toggleUploadMenu={toggleUploadMenu}/>
<ImpressumButton setNavOpen={setNavOpen} toggleImpressum={toggleImpressum} isDropdown={true} />
<PrivacyButton setNavOpen={setNavOpen} togglePrivacy={togglePrivacy} isDropdown={true} />
<PreferencesButton setNavOpen={setNavOpen} togglePreferencesPopup={togglePreferencesPopup}/>
</div>
</div>
}
/** the navigation bar in a level */
export function LevelAppBar({isLoading, levelTitle, toggleImpressum, toggleInfo, togglePreferencesPopup, pageNumber=undefined, setPageNumber=undefined} : {
export function LevelAppBar({isLoading, levelTitle, toggleImpressum, togglePrivacy, toggleInfo, togglePreferencesPopup, pageNumber=undefined, setPageNumber=undefined} : {
isLoading: boolean,
levelTitle: string,
toggleImpressum: any,
togglePrivacy: any,
toggleInfo: any,
togglePreferencesPopup: any,
pageNumber?: number,
@@ -282,6 +290,7 @@ export function LevelAppBar({isLoading, levelTitle, toggleImpressum, toggleInfo,
<InputModeButton setNavOpen={setNavOpen} isDropdown={true}/>
<GameInfoButton setNavOpen={setNavOpen} toggleInfo={toggleInfo}/>
<ImpressumButton setNavOpen={setNavOpen} toggleImpressum={toggleImpressum} isDropdown={true} />
<PrivacyButton setNavOpen={setNavOpen} togglePrivacy={togglePrivacy} isDropdown={true} />
<PreferencesButton setNavOpen={setNavOpen} togglePreferencesPopup={togglePreferencesPopup}/>
</div>
</> :
@@ -303,6 +312,7 @@ export function LevelAppBar({isLoading, levelTitle, toggleImpressum, toggleInfo,
<div className={'menu dropdown' + (navOpen ? '' : ' hidden')}>
<GameInfoButton setNavOpen={setNavOpen} toggleInfo={toggleInfo}/>
<ImpressumButton setNavOpen={setNavOpen} toggleImpressum={toggleImpressum} isDropdown={true} />
<PrivacyButton setNavOpen={setNavOpen} togglePrivacy={togglePrivacy} isDropdown={true} />
<PreferencesButton setNavOpen={setNavOpen} togglePreferencesPopup={togglePreferencesPopup}/>
</div>
</>
@@ -35,6 +35,12 @@ monaco.languages.register({
extensions: ['.leancmd']
})
// register Monaco languages // TODO: JE. I dont understand why I suddenly had to add this when it worked without before.
monaco.languages.register({
id: 'lean4',
extensions: ['.lean']
})
// map of monaco "language id's" to TextMate scopeNames
const grammars = new Map()
grammars.set('lean4', 'source.lean')
+10 -3
View File
@@ -11,12 +11,12 @@ import '../css/landing_page.css'
import bgImage from '../assets/bg.jpg'
import Markdown from './markdown';
import {PrivacyPolicyPopup} from './popup/privacy_policy'
import {PrivacyPolicyPopup, ImpressumPopup} from './popup/privacy_policy'
import { GameTile, useGetGameInfoQuery } from '../state/api'
import path from 'path';
import { PreferencesPopup } from './popup/preferences';
import { ImpressumButton, MenuButton, PreferencesButton } from './app_bar';
import { ImpressumButton, MenuButton, PreferencesButton, PrivacyButton } from './app_bar';
import ReactCountryFlag from 'react-country-flag';
import lean4gameConfig from '../config.json'
import i18next from 'i18next';
@@ -91,11 +91,15 @@ function LandingPage() {
const navigate = useNavigate();
const [impressumPopup, setImpressumPopup] = React.useState(false);
const [privacyPopup, setPrivacyPopup] = React.useState(false);
const [preferencesPopup, setPreferencesPopup] = React.useState(false);
const [navOpen, setNavOpen] = React.useState(false);
const openImpressum = () => setImpressumPopup(true);
const closeImpressum = () => setImpressumPopup(false);
const toggleImpressum = () => setImpressumPopup(!impressumPopup);
const openPrivacy = () => setPrivacyPopup(true);
const closePrivacy = () => setPrivacyPopup(false);
const togglePrivacy = () => setPrivacyPopup(!privacyPopup);
const closePreferencesPopup = () => setPreferencesPopup(false);
const togglePreferencesPopup = () => setPreferencesPopup(!preferencesPopup);
@@ -128,6 +132,7 @@ function LandingPage() {
<MenuButton navOpen={navOpen} setNavOpen={setNavOpen}/>
<div className={'menu dropdown' + (navOpen ? '' : ' hidden')}>
<ImpressumButton setNavOpen={setNavOpen} toggleImpressum={toggleImpressum} isDropdown={true} />
<PrivacyButton setNavOpen={setNavOpen} togglePrivacy={togglePrivacy} isDropdown={true} />
<PreferencesButton setNavOpen={setNavOpen} togglePreferencesPopup={togglePreferencesPopup}/>
</div>
</nav>
@@ -214,7 +219,9 @@ function LandingPage() {
<footer>
{/* Do not translate "Impressum", it's needed for German GDPR */}
<a className="link" onClick={openImpressum}>Impressum</a>
{impressumPopup? <PrivacyPolicyPopup handleClose={closeImpressum} />: null}
<a className="link" onClick={openPrivacy}>{t("Privacy Policy")}</a>
{privacyPopup? <PrivacyPolicyPopup handleClose={closePrivacy} />: null}
{impressumPopup? <ImpressumPopup handleClose={closeImpressum} />: null}
{preferencesPopup ? <PreferencesPopup handleClose={closePreferencesPopup} /> : null}
</footer>
</div>
+17 -10
View File
@@ -33,7 +33,7 @@ import { DeletedChatContext, InputModeContext, PreferencesContext, MonacoEditorC
import { DualEditor } from './infoview/main'
import { GameHint, InteractiveGoalsWithHints, ProofState } from './infoview/rpc_api'
import { DeletedHints, Hint, Hints, MoreHelpButton, filterHints } from './hints'
import { PrivacyPolicyPopup } from './popup/privacy_policy'
import { ImpressumPopup, PrivacyPolicyPopup } from './popup/privacy_policy'
import path from 'path';
import '@fontsource/roboto/300.css'
@@ -75,10 +75,12 @@ function Level() {
// pop-ups
const [impressum, setImpressum] = React.useState(false)
const [privacy, setPrivacy] = React.useState(false)
const [info, setInfo] = React.useState(false)
const [preferencesPopup, setPreferencesPopup] = React.useState(false)
function closeImpressum() {setImpressum(false)}
function closePrivacy() {setPrivacy(false)}
function closeInfo() {setInfo(false)}
function closePreferencesPopup() {setPreferencesPopup(false)}
function toggleImpressum() {setImpressum(!impressum)}
@@ -87,9 +89,10 @@ function Level() {
return <WorldLevelIdContext.Provider value={{worldId, levelId}}>
{levelId == 0 ?
<Introduction impressum={impressum} setImpressum={setImpressum} toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup} /> :
<PlayableLevel key={`${worldId}/${levelId}`} impressum={impressum} setImpressum={setImpressum} toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup}/>}
{impressum ? <PrivacyPolicyPopup handleClose={closeImpressum} /> : null}
<Introduction impressum={impressum} setImpressum={setImpressum} privacy={privacy} setPrivacy={setPrivacy} toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup} /> :
<PlayableLevel key={`${worldId}/${levelId}`} impressum={impressum} setImpressum={setImpressum} privacy={privacy} setPrivacy={setPrivacy} toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup}/>}
{impressum ? <ImpressumPopup handleClose={closeImpressum} /> : null}
{privacy ? <PrivacyPolicyPopup handleClose={closePrivacy} /> : null}
{info ? <InfoPopup info={gameInfo.data?.info} handleClose={closeInfo}/> : null}
{preferencesPopup ? <PreferencesPopup handleClose={closePreferencesPopup} /> : null}
</WorldLevelIdContext.Provider>
@@ -212,7 +215,7 @@ function ExercisePanel({codeviewRef, visible=true}: {codeviewRef: React.MutableR
</div>
}
function PlayableLevel({impressum, setImpressum, toggleInfo, togglePreferencesPopup}) {
function PlayableLevel({impressum, setImpressum, privacy, setPrivacy, toggleInfo, togglePreferencesPopup}) {
let { t } = useTranslation()
const codeviewRef = useRef<HTMLDivElement>(null)
const gameId = React.useContext(GameIdContext)
@@ -251,6 +254,7 @@ function PlayableLevel({impressum, setImpressum, toggleInfo, togglePreferencesPo
// impressum pop-up
function toggleImpressum() {setImpressum(!impressum)}
function togglePrivacy() {setPrivacy(!privacy)}
// When clicking on an inventory item, the inventory is overlayed by the item's doc.
// If this state is set to a pair `(name, type)` then the according doc will be open.
@@ -419,8 +423,9 @@ function PlayableLevel({impressum, setImpressum, toggleInfo, togglePreferencesPo
isLoading={level.isLoading}
levelTitle={(mobile ? "" : t("Level")) + ` ${levelId} / ${gameInfo.data?.worldSize[worldId]}` +
(level?.data?.title && ` : ${t(level?.data?.title, {ns: gameId})}`)}
toggleImpressum={toggleImpressum}
toggleInfo={toggleInfo}
toggleImpressum={toggleImpressum}
togglePrivacy={togglePrivacy}
toggleInfo={toggleInfo}
togglePreferencesPopup={togglePreferencesPopup}
/>
{mobile?
@@ -479,7 +484,7 @@ function IntroductionPanel({gameInfo}) {
export default Level
/** The site with the introduction text of a world */
function Introduction({impressum, setImpressum, toggleInfo, togglePreferencesPopup}) {
function Introduction({impressum, setImpressum, privacy, setPrivacy, toggleInfo, togglePreferencesPopup}) {
let { t } = useTranslation()
const gameId = React.useContext(GameIdContext)
@@ -497,9 +502,11 @@ function Introduction({impressum, setImpressum, toggleInfo, togglePreferencesPop
const toggleImpressum = () => {
setImpressum(!impressum)
}
const togglePrivacy = () => {
setPrivacy(!privacy)
}
return <>
<LevelAppBar isLoading={gameInfo.isLoading} levelTitle={t("Introduction")} toggleImpressum={toggleImpressum} toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup}/>
<LevelAppBar isLoading={gameInfo.isLoading} levelTitle={t("Introduction")} toggleImpressum={toggleImpressum} togglePrivacy={togglePrivacy} toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup}/>
{gameInfo.isLoading ?
<div className="app-content loading"><CircularProgress /></div>
: mobile ?
+29 -1
View File
@@ -4,7 +4,7 @@
import * as React from 'react'
import { Typography } from '@mui/material'
import Markdown from '../markdown'
import { useTranslation } from 'react-i18next'
import { Trans, useTranslation } from 'react-i18next'
import { GameIdContext } from '../../app'
/** Pop-up that is displaying the Game Info.
@@ -22,6 +22,34 @@ export function InfoPopup ({info, handleClose}: {info: string, handleClose: () =
<div className="codicon codicon-close modal-close" onClick={handleClose}></div>
<Typography variant="body1" component="div" className="welcome-text">
<Markdown>{t(info, {ns: gameId})}</Markdown>
<hr />
<Trans>
<h2>Progress saving</h2>
<p>
The game stores your progress in your local browser storage. If you delete it, your progress will be lost!<br />
Warning: In most browsers, deleting cookies will also clear the local storage (or "local site data").
Make sure to download your game progress first!
</p>
<h2>Development</h2>
<p>The game engine has been created by <strong>Alexander Bentkamp</strong>, <strong>Jon Eugster</strong>.
On a prototype by <strong>Patrick Massot</strong>.
</p>
<p>
The source code of this Lean game engine
is <a href="https://github.com/leanprover-community/lean4game" target="_blank">available on Github</a>.
If you experience any problems, please
file an <a href="https://github.com/leanprover-community/lean4game/issues" target="_blank">Issue on Github</a> or
get directly in contact.
</p>
<h2>Funding</h2>
<p>
The game engine has been developed as part of the
project <a href="https://hhu-adam.github.io/" target="_blank">ADAM: Anticipating the Digital
Age of Mathematics</a> at
Heinrich-Heine-Universität Düsseldorf. It is funded by
the <i>Stiftung Innovation in der Hochschullehre</i> as part of project <i>Freiraum 2022</i>.
</p>
</Trans>
</Typography>
</div>
</div>
+93 -42
View File
@@ -4,6 +4,7 @@
import { faShield } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as React from 'react'
import { Trans, useTranslation } from 'react-i18next';
/** Pop-up that is displayed when opening the privacy policy.
*
@@ -13,49 +14,99 @@ import * as React from 'react'
* Note: Do not translate the Impressum!
*/
export function PrivacyPolicyPopup ({handleClose}: {handleClose: () => void}) {
let {t, i18n} = useTranslation()
function content (lng = i18n.language) {
const tt = i18n.getFixedT(lng);
return <Trans t={tt} >
<h2>Privacy Policy</h2>
<p>
Our server collects metadata (such as IP address, browser, operating system)
and the data that the user enters into the editor. The data is used to
compute the Lean output and display it to the user. The information will be stored
as long as the user stays on our website and will be deleted immediately afterwards.
We keep logs to improve our software, but the contained data is anonymized.
</p>
<p>
We do not use cookies, but your game progress is stored in the browser
as site data. Your game progress is not saved on the server; if you delete
your browser storage, it is completely gone.
</p>
<p>Our server is located in Germany.</p>
<p>
<strong>Contact:</strong><br />
Marcus Zibrowius, Jon Eugster<br />
Mathematisches Institut der Heinrich-Heine-Universität Düsseldorf<br />
Universitätsstr. 1<br />
40225 Düsseldorf<br />
Germany<br />
+49 211 81-14690<br />
<a href="https://www.math.hhu.de/en/lehrstuehle-/-personen-/-ansprechpartner/innen/lehrstuehle-des-mathematischen-instituts/lehrstuhl-fuer-algebraische-geometrie/team">Contact Details</a>
</p>
</Trans>
}
return <div className="privacy-policy modal-wrapper">
<div className="modal-backdrop" onClick={handleClose} />
<div className="modal">
<div className="codicon codicon-close modal-close" onClick={handleClose}></div>
<h2>Privacy Policy &amp; Impressum</h2>
<p>
Our server collects metadata (such as IP address, browser, operating system)
and the data that the user enters into the editor. The data is used to
compute the Lean output and display it to the user. The information will be stored
as long as the user stays on our website and will be deleted immediately afterwards.
We keep logs to improve our software, but the contained data is anonymized.
</p>
<p>
We do not use cookies, but your game progress is stored in the browser
as site data. Your game progress is not saved on the server; if you delete
your browser storage, it is completely gone.
</p>
<p>Our server is located in Germany.</p>
<p>
<strong>Contact information:</strong><br />
Alexander Bentkamp, Jon Eugster<br />
Mathematisches Institut der Heinrich-Heine-Universität Düsseldorf<br />
Universitätsstr. 1<br />
40225 Düsseldorf<br />
Germany<br />
+49 211 81-12173<br />
<a href="https://www.math.hhu.de/en/lehrstuehle-/-personen-/-ansprechpartner/innen/lehrstuehle-des-mathematischen-instituts/lehrstuhl-fuer-algebraische-geometrie/team/jon-eugster">Contact Details</a>
</p>
<h2>Development &amp; Funding</h2>
<p>
The source code of this Lean game engine
is <a href="https://github.com/leanprover-community/lean4game" target="_blank">available on Github</a>.
If you experience any problems, please
file an <a href="https://github.com/leanprover-community/lean4game/issues" target="_blank">Issue on Github</a> or
get directly in contact.
</p>
<p>
The game engine has been developed as part of the
project <a href="https://hhu-adam.github.io/" target="_blank">ADAM: Anticipating the Digital
Age of Mathematics</a> at
Heinrich-Heine-Universität Düsseldorf. It is funded by
the <i>Stiftung Innovation in der Hochschullehre</i> as part of project <i>Freiraum 2022</i>.
</p>
</div>
<div className="modal">
<div className="codicon codicon-close modal-close" onClick={handleClose}></div>
{i18n.language != 'en' && <>
<p><i>(English version below)</i></p>
{content()}
<hr />
</>}
{content('en')}
</div>
</div>
}
/** Pop-up that is displayed when opening the privacy policy.
*
* `handleClose` is the function to close it again because it's open/closed state is
* controlled by the containing element.
*
* Note: Do not translate the Impressum!
*/
export function ImpressumPopup ({handleClose}: {handleClose: () => void}) {
let {t, i18n} = useTranslation()
function content (lng = i18n.language) {
const tt = i18n.getFixedT(lng);
return <Trans t={tt} >
<h2>Impressum</h2>
<p>
<strong>Contact:</strong><br />
Marcus Zibrowius, Jon Eugster<br />
Mathematisches Institut der Heinrich-Heine-Universität Düsseldorf<br />
Universitätsstr. 1<br />
40225 Düsseldorf<br />
Germany<br />
+49 211 81-14690<br />
<a href="https://www.math.hhu.de/en/lehrstuehle-/-personen-/-ansprechpartner/innen/lehrstuehle-des-mathematischen-instituts/lehrstuhl-fuer-algebraische-geometrie/team">Contact Details</a>
</p>
<p>
<strong>Legal form:</strong><br />
The Heinrich Heine University Düsseldorf is a corporation under public law. It is legally represented by the Rector Prof. Dr. Anja Steinbeck. The responsible supervisory authority is the Ministry of Culture and Science of North Rhine-Westphalia, Völklinger Straße 49, 40221 Düsseldorf.
</p>
<p>
<strong>VAT identification number:</strong><br />
according to §27a Sales Tax Act<br />
DE 811222416
</p>
<p><a href="https://www.hhu.de/impressum" target="_blank">Impressum HHU</a></p>
</Trans>
}
return <div className="privacy-policy modal-wrapper">
<div className="modal-backdrop" onClick={handleClose} />
<div className="modal">
<div className="codicon codicon-close modal-close" onClick={handleClose}></div>
{i18n.language != 'en' && <>
<p><i>(English version below)</i></p>
{content()}
<hr />
</>}
{content('en')}
</div>
</div>
}
+7 -3
View File
@@ -14,7 +14,7 @@ import { PreferencesContext } from './infoview/context'
import { InventoryPanel } from './inventory'
import { ErasePopup } from './popup/erase'
import { InfoPopup } from './popup/game_info'
import { PrivacyPolicyPopup } from './popup/privacy_policy'
import { ImpressumPopup, PrivacyPolicyPopup } from './popup/privacy_policy'
import { RulesHelpPopup } from './popup/rules_help'
import { UploadPopup } from './popup/upload'
import { PreferencesPopup} from "./popup/preferences"
@@ -86,6 +86,7 @@ function Welcome() {
// pop-ups
const [eraseMenu, setEraseMenu] = React.useState(false)
const [impressum, setImpressum] = React.useState(false)
const [privacy, setPrivacy] = React.useState(false)
const [info, setInfo] = React.useState(false)
const [rulesHelp, setRulesHelp] = React.useState(false)
const [uploadMenu, setUploadMenu] = React.useState(false)
@@ -93,12 +94,14 @@ function Welcome() {
function closeEraseMenu() {setEraseMenu(false)}
function closeImpressum() {setImpressum(false)}
function closePrivacy() {setPrivacy(false)}
function closeInfo() {setInfo(false)}
function closeRulesHelp() {setRulesHelp(false)}
function closeUploadMenu() {setUploadMenu(false)}
function closePreferencesPopup() {setPreferencesPopup(false)}
function toggleEraseMenu() {setEraseMenu(!eraseMenu)}
function toggleImpressum() {setImpressum(!impressum)}
function togglePrivacy() {setPrivacy(!privacy)}
function toggleInfo() {setInfo(!info)}
function toggleUploadMenu() {setUploadMenu(!uploadMenu)}
function togglePreferencesPopup() {setPreferencesPopup(!preferencesPopup)}
@@ -115,7 +118,7 @@ function Welcome() {
<CircularProgress />
</Box>
: <>
<WelcomeAppBar pageNumber={pageNumber} setPageNumber={setPageNumber} gameInfo={gameInfo.data} toggleImpressum={toggleImpressum}
<WelcomeAppBar pageNumber={pageNumber} setPageNumber={setPageNumber} gameInfo={gameInfo.data} toggleImpressum={toggleImpressum} togglePrivacy={togglePrivacy}
toggleEraseMenu={toggleEraseMenu} toggleUploadMenu={toggleUploadMenu}
toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup}/>
<div className="app-content">
@@ -139,7 +142,8 @@ function Welcome() {
</Split>
}
</div>
{impressum ? <PrivacyPolicyPopup handleClose={closeImpressum} /> : null}
{impressum ? <ImpressumPopup handleClose={closeImpressum} /> : null}
{privacy ? <PrivacyPolicyPopup handleClose={closePrivacy} /> : null}
{rulesHelp ? <RulesHelpPopup handleClose={closeRulesHelp} /> : null}
{eraseMenu? <ErasePopup handleClose={closeEraseMenu}/> : null}
{uploadMenu? <UploadPopup handleClose={closeUploadMenu}/> : null}
+6
View File
@@ -145,3 +145,9 @@ em {
.privacy-policy {
z-index: 10;
}
.modal hr {
margin-top: 3rem;
margin-bottom: 3rem;
border-color: var(--vscode-breadcrumb-foreground);
}