From 43c0cf6533dfd7b474c6cbe91b269e5b08606cfb Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Tue, 10 Oct 2023 20:39:34 +0900 Subject: [PATCH 01/21] add react-ga4, add test ad spaces --- index.html | 1 + package.json | 1 + scripts/json-linter | 2 +- src/@types/globals.d.ts | 1 + src/app.tsx | 2 ++ src/components/AdSpace.tsx | 29 +++++++++++++++++++++++++++++ src/components/AdSpaceFloating.tsx | 30 ++++++++++++++++++++++++++++++ src/components/Layout.tsx | 15 ++++++++++++--- src/components/Tracking.tsx | 29 +++++++++++++++++++++++++++++ src/json/i18n/en/en.json | 6 ++++-- src/pages/about/About.tsx | 9 --------- src/pages/about/Privacy.tsx | 18 ++++++++++++++++++ vite.config.ts | 1 + 13 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 src/components/AdSpace.tsx create mode 100644 src/components/AdSpaceFloating.tsx create mode 100644 src/components/Tracking.tsx create mode 100644 src/pages/about/Privacy.tsx diff --git a/index.html b/index.html index f05ea74bd..ba84d08c3 100644 --- a/index.html +++ b/index.html @@ -51,6 +51,7 @@ property="og:locale:alternate" content="ru_RU" /> + /dev/null fi if [[ -z $(command -v json-linter) ]]; then diff --git a/src/@types/globals.d.ts b/src/@types/globals.d.ts index 0db5d592c..251e164ba 100644 --- a/src/@types/globals.d.ts +++ b/src/@types/globals.d.ts @@ -1,2 +1,3 @@ declare const DB_BUILD_TIME: number; declare const DB_DEVMODE: boolean; +declare const DB_GA4_MEASUREMENT_ID: string; diff --git a/src/app.tsx b/src/app.tsx index f44b50e18..3a6e5c16a 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -22,6 +22,8 @@ import BackgroundTasks from "@src/components/BackgroundTasks"; import Favorites from "@src/pages/favorites/Favorites"; import useIsMobile from "@src/hooks/is-mobile"; import log from "@src/utils/logger"; +import { useAppSelector } from "@src/hooks/redux"; +import { selectConfiguration } from "@src/reducers/configuration/configuration-slice"; import SomethingWentWrong from "@src/components/SomethingWentWrong"; import { ErrorBoundary } from "react-error-boundary"; import useIsLightMode from "@src/hooks/light-mode"; diff --git a/src/components/AdSpace.tsx b/src/components/AdSpace.tsx new file mode 100644 index 000000000..843edd80e --- /dev/null +++ b/src/components/AdSpace.tsx @@ -0,0 +1,29 @@ +import { FeaturedVideo } from "@mui/icons-material"; +import { Box, useTheme } from "@mui/material"; + +const AdSpace = () => { + const theme = useTheme(); + + if (DB_DEVMODE) { + return ( + + + + ); + } + + return null; +}; + +export default AdSpace; diff --git a/src/components/AdSpaceFloating.tsx b/src/components/AdSpaceFloating.tsx new file mode 100644 index 000000000..795f293e2 --- /dev/null +++ b/src/components/AdSpaceFloating.tsx @@ -0,0 +1,30 @@ +import { Box, useTheme } from "@mui/material"; +import AdSpace from "@src/components/AdSpace"; +import useIsMobile from "@src/hooks/is-mobile"; + +const AdSpaceFloating = () => { + const isMobile = useIsMobile(); + const theme = useTheme(); + + if (!isMobile) { + return null; + } + + return ( + + + + ); +}; + +export default AdSpaceFloating; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index aeb89b5ea..597bfb8fc 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -9,6 +9,7 @@ import { ManageSearch, Menu, Settings, + Shield, Stars, } from "@mui/icons-material"; import { @@ -30,10 +31,13 @@ import { Typography, useTheme, } from "@mui/material"; +import AdSpace from "@src/components/AdSpace"; +import AdSpaceFloating from "@src/components/AdSpaceFloating"; import BuildMenu from "@src/components/BuildMenu"; import LinkBox from "@src/components/LinkBox"; import { drawerWidth } from "@src/components/theme"; import { crowdinLink, discordServerUrl, githubUrl, xTwitterUrl } from "@src/constants"; +import Tracking from "@src/components/Tracking"; import dauntlessBuilderData from "@src/data/Data"; import useDevMode from "@src/hooks/dev-mode"; import useIsMobile from "@src/hooks/is-mobile"; @@ -76,6 +80,7 @@ const Layout: React.FC = ({ children }) => { { icon: , link: "/b/finder", text: t("drawer.build-finder") }, { icon: , link: "/b/meta", text: t("drawer.meta-builds") }, { icon: , link: "/about", text: t("drawer.about") }, + { icon: , link: "/privacy", text: t("drawer.privacy") }, { icon: , link: "/settings", text: t("drawer.settings") }, ]; @@ -178,12 +183,13 @@ const Layout: React.FC = ({ children }) => { ))} + + {isMobile ? : } + = ({ children }) => { {children} + + + ); }; diff --git a/src/components/Tracking.tsx b/src/components/Tracking.tsx new file mode 100644 index 000000000..c3aa08613 --- /dev/null +++ b/src/components/Tracking.tsx @@ -0,0 +1,29 @@ +import { useAppSelector } from "@src/hooks/redux"; +import { selectConfiguration } from "@src/reducers/configuration/configuration-slice"; +import log from "@src/utils/logger"; +import ReactGA from "react-ga4"; + +const Tracking = () => { + const configuration = useAppSelector(selectConfiguration); + + // if user has do not track set, don't do anything + if (navigator.doNotTrack !== "1") { + return null; + } + + // tracking has not been enabled, skip... + if (!configuration.tracking.enabled) { + return; + } + + if (DB_GA4_MEASUREMENT_ID) { + ReactGA.initialize(DB_GA4_MEASUREMENT_ID, { + testMode: DB_DEVMODE, + }); + log.debug("enabled GA4"); + } + + return null; +}; + +export default Tracking; diff --git a/src/json/i18n/en/en.json b/src/json/i18n/en/en.json index 9680622cc..819b14f1e 100644 --- a/src/json/i18n/en/en.json +++ b/src/json/i18n/en/en.json @@ -36,6 +36,7 @@ "meta-builds": "$t(pages.metabuilds.title)", "my-builds": "$t(pages.favorites.title)", "new-build": "New Build", + "privacy": "$t(pages.privacy.title)", "settings": "$t(pages.settings.title)", "trials": "$t(pages.trials.title)" }, @@ -59,8 +60,6 @@ "contributors": "Contributors", "dependencies": "Dependencies ({{number}})", "main-text": "Dauntless Builder is free and open source software licensed under the terms of the AGPLv3 license.", - "privacy": "Privacy", - "privacy-text": "We are not collecting any data.", "source-code": "Source Code", "title": "About", "translators": "Translators" @@ -233,6 +232,9 @@ "newbuild": { "title": "Create a new build" }, + "privacy": { + "title": "Privacy" + }, "settings": { "community-language": "This language is not officially supported by Dauntless, but members of our community decided to add a translation for Dauntless Builder regardless.", "data": "Application Data", diff --git a/src/pages/about/About.tsx b/src/pages/about/About.tsx index 2106b0ceb..225bc9777 100644 --- a/src/pages/about/About.tsx +++ b/src/pages/about/About.tsx @@ -168,15 +168,6 @@ const About: React.FC = () => { - - {t("pages.about.privacy")} - - - {t("pages.about.privacy-text")} - { + const { t } = useTranslation(); + + return ( + + + + Lorem ipsum dolor sit amet + + ); +}; + +export default Privacy; diff --git a/vite.config.ts b/vite.config.ts index f080209e1..48a529b2c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,6 +22,7 @@ export default defineConfig(({ command, mode }) => { define: { DB_BUILD_TIME: Date.now(), DB_DEVMODE: isDevMode, + DB_GA4_MEASUREMENT_ID: JSON.stringify(process.env["DB_GA4_MEASUREMENT_ID"]), }, plugins: [ react({ babel: { plugins: [jotaiDebugLabel, jotaiReactRefresh] } }), From de486a9a5e91cd7201103c50a5b69ad1de91d205 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Fri, 13 Oct 2023 23:06:50 +0900 Subject: [PATCH 02/21] add some headlines --- src/pages/about/Privacy.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/about/Privacy.tsx b/src/pages/about/Privacy.tsx index 800e2ebf5..f003f0579 100644 --- a/src/pages/about/Privacy.tsx +++ b/src/pages/about/Privacy.tsx @@ -11,6 +11,11 @@ const Privacy: React.FC = () => { Lorem ipsum dolor sit amet + + + + + ); }; From 3d128db22f3811e975cf3a8fb22dabfb3ba400c9 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Thu, 19 Oct 2023 21:32:13 +0900 Subject: [PATCH 03/21] move ad space to right side if enough space is available --- src/components/AdSpaceFloating.tsx | 44 ++++++++++++++++++++++-------- src/components/BuildMenu.tsx | 5 +++- src/components/Layout.tsx | 9 ++++-- src/components/Spacer.tsx | 6 ++++ src/hooks/window-size.ts | 15 ++++++++++ 5 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 src/components/Spacer.tsx create mode 100644 src/hooks/window-size.ts diff --git a/src/components/AdSpaceFloating.tsx b/src/components/AdSpaceFloating.tsx index 795f293e2..819cdc48d 100644 --- a/src/components/AdSpaceFloating.tsx +++ b/src/components/AdSpaceFloating.tsx @@ -1,27 +1,49 @@ import { Box, useTheme } from "@mui/material"; import AdSpace from "@src/components/AdSpace"; import useIsMobile from "@src/hooks/is-mobile"; +import useWindowSize from "@src/hooks/window-size"; +import React from "react"; + +export const adSpaceRightSideMinSize = 300; +export const adSpaceMobileBannerHeight = 96; const AdSpaceFloating = () => { - const isMobile = useIsMobile(); const theme = useTheme(); + const isMobile = useIsMobile(); + const { width } = useWindowSize(); + + const rightSideSpace = (width - theme.breakpoints.values.xl) * 0.5; + const rightSideHasEnoughSpace = rightSideSpace > adSpaceRightSideMinSize; + + const baseStyle = { + background: theme.palette.background.default, + display: "flex", + position: "fixed", + }; - if (!isMobile) { + if (!isMobile && !rightSideHasEnoughSpace) { return null; } - return ( - + } + : { + bottom: 0, + right: 0, + top: 64, + width: `${rightSideSpace - adSpaceRightSideMinSize * 0.5}px`, + }), + }; + + return ( + ); diff --git a/src/components/BuildMenu.tsx b/src/components/BuildMenu.tsx index 009887bab..2e47471df 100644 --- a/src/components/BuildMenu.tsx +++ b/src/components/BuildMenu.tsx @@ -1,5 +1,6 @@ import { Bookmark, BookmarkBorder, ContentCopy, Undo } from "@mui/icons-material"; import { Fab, IconButton, useTheme } from "@mui/material"; +import { adSpaceMobileBannerHeight } from "@src/components/AdSpaceFloating"; import InputDialog from "@src/components/InputDialog"; import useIsMobile from "@src/hooks/is-mobile"; import { buildModelView, lastSelectedBuildModelView } from "@src/state/build"; @@ -54,6 +55,8 @@ const BuildMenu: React.FC = () => { setInputDialogOpen(false); }; + const adsEnabled = true; + return ( <> {isUserEditedBuild ? null : ( @@ -93,7 +96,7 @@ const BuildMenu: React.FC = () => { color="primary" onClick={handleCopyToClipboardClicked} sx={{ - bottom: theme.spacing(2), + bottom: adsEnabled ? `${adSpaceMobileBannerHeight}px` : theme.spacing(2), position: "fixed", right: theme.spacing(3), }} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 597bfb8fc..b1b70a598 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -32,9 +32,10 @@ import { useTheme, } from "@mui/material"; import AdSpace from "@src/components/AdSpace"; -import AdSpaceFloating from "@src/components/AdSpaceFloating"; +import AdSpaceFloating, { adSpaceRightSideMinSize } from "@src/components/AdSpaceFloating"; import BuildMenu from "@src/components/BuildMenu"; import LinkBox from "@src/components/LinkBox"; +import Spacer from "@src/components/Spacer"; import { drawerWidth } from "@src/components/theme"; import { crowdinLink, discordServerUrl, githubUrl, xTwitterUrl } from "@src/constants"; import Tracking from "@src/components/Tracking"; @@ -43,6 +44,7 @@ import useDevMode from "@src/hooks/dev-mode"; import useIsMobile from "@src/hooks/is-mobile"; import { currentLanguage, getNativeLanguageName, isBetaLanguage, Language } from "@src/i18n"; import { favoritesView } from "@src/state/favorites"; +import useWindowSize from "@src/hooks/window-size"; import log from "@src/utils/logger"; import { useAtomValue } from "jotai"; import React, { ReactNode, useState } from "react"; @@ -64,6 +66,7 @@ const Layout: React.FC = ({ children }) => { const theme = useTheme(); const isMobile = useIsMobile(); + const { width } = useWindowSize(); const [open, setOpen] = useState(false); const { t } = useTranslation(); const devMode = useDevMode(); @@ -84,6 +87,8 @@ const Layout: React.FC = ({ children }) => { { icon: , link: "/settings", text: t("drawer.settings") }, ]; + const showLeftSideAdSpace = (width - theme.breakpoints.values.xl) * 0.5 <= adSpaceRightSideMinSize; + return ( @@ -184,7 +189,7 @@ const Layout: React.FC = ({ children }) => { ))} - {isMobile ? : } + {isMobile ? : showLeftSideAdSpace ? : } ; + +export default React.memo(Spacer); diff --git a/src/hooks/window-size.ts b/src/hooks/window-size.ts new file mode 100644 index 000000000..e4a51c7c5 --- /dev/null +++ b/src/hooks/window-size.ts @@ -0,0 +1,15 @@ +import { useState } from "react"; + +const useWindowSize = () => { + const [width, setWidth] = useState(window.innerWidth); + const [height, setHeight] = useState(window.innerHeight); + + window.addEventListener("resize", () => { + setWidth(window.innerWidth); + setHeight(window.innerHeight); + }); + + return { height, width }; +}; + +export default useWindowSize; From d29aec84a2bb6e2b5732dacff7401a136e3e2e55 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Thu, 19 Oct 2023 21:44:38 +0900 Subject: [PATCH 04/21] add new icon, add global var to disable ads --- src/@types/globals.d.ts | 1 + src/components/AdSpace.tsx | 4 ++++ src/components/AdSpaceFloating.tsx | 4 ++++ src/components/BuildMenu.tsx | 4 +--- src/components/Layout.tsx | 2 +- vite.config.ts | 1 + 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/@types/globals.d.ts b/src/@types/globals.d.ts index 251e164ba..081b31259 100644 --- a/src/@types/globals.d.ts +++ b/src/@types/globals.d.ts @@ -1,3 +1,4 @@ declare const DB_BUILD_TIME: number; declare const DB_DEVMODE: boolean; declare const DB_GA4_MEASUREMENT_ID: string; +declare const DB_ENABLE_ADS: string; diff --git a/src/components/AdSpace.tsx b/src/components/AdSpace.tsx index 843edd80e..51c7dd04a 100644 --- a/src/components/AdSpace.tsx +++ b/src/components/AdSpace.tsx @@ -4,6 +4,10 @@ import { Box, useTheme } from "@mui/material"; const AdSpace = () => { const theme = useTheme(); + if (!DB_ENABLE_ADS) { + return null; + } + if (DB_DEVMODE) { return ( { const isMobile = useIsMobile(); const { width } = useWindowSize(); + if (!DB_ENABLE_ADS) { + return null; + } + const rightSideSpace = (width - theme.breakpoints.values.xl) * 0.5; const rightSideHasEnoughSpace = rightSideSpace > adSpaceRightSideMinSize; diff --git a/src/components/BuildMenu.tsx b/src/components/BuildMenu.tsx index 2e47471df..692d2a3f9 100644 --- a/src/components/BuildMenu.tsx +++ b/src/components/BuildMenu.tsx @@ -55,8 +55,6 @@ const BuildMenu: React.FC = () => { setInputDialogOpen(false); }; - const adsEnabled = true; - return ( <> {isUserEditedBuild ? null : ( @@ -96,7 +94,7 @@ const BuildMenu: React.FC = () => { color="primary" onClick={handleCopyToClipboardClicked} sx={{ - bottom: adsEnabled ? `${adSpaceMobileBannerHeight}px` : theme.spacing(2), + bottom: DB_ENABLE_ADS ? `${adSpaceMobileBannerHeight}px` : theme.spacing(2), position: "fixed", right: theme.spacing(3), }} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index b1b70a598..a3618d5cc 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -87,7 +87,7 @@ const Layout: React.FC = ({ children }) => { { icon: , link: "/settings", text: t("drawer.settings") }, ]; - const showLeftSideAdSpace = (width - theme.breakpoints.values.xl) * 0.5 <= adSpaceRightSideMinSize; + const showLeftSideAdSpace = DB_ENABLE_ADS && (width - theme.breakpoints.values.xl) * 0.5 <= adSpaceRightSideMinSize; return ( diff --git a/vite.config.ts b/vite.config.ts index 48a529b2c..168ae92ca 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,6 +22,7 @@ export default defineConfig(({ command, mode }) => { define: { DB_BUILD_TIME: Date.now(), DB_DEVMODE: isDevMode, + DB_ENABLE_ADS: process.env["DB_ENABLE_ADS"] === "true", DB_GA4_MEASUREMENT_ID: JSON.stringify(process.env["DB_GA4_MEASUREMENT_ID"]), }, plugins: [ From 1f1838f470e2b21b27d2c24cdc9072932166bc38 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Thu, 19 Oct 2023 23:47:48 +0900 Subject: [PATCH 05/21] add privacy policy --- src/json/i18n/en/en.json | 4 +- src/pages/about/Privacy.tsx | 30 ++++-- src/pages/about/privacy.html | 193 +++++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 8 deletions(-) create mode 100644 src/pages/about/privacy.html diff --git a/src/json/i18n/en/en.json b/src/json/i18n/en/en.json index 819b14f1e..6e229389b 100644 --- a/src/json/i18n/en/en.json +++ b/src/json/i18n/en/en.json @@ -45,6 +45,7 @@ "dauntless-version": "Dauntless v{{version}}", "discord-server": "Dauntless Builder Discord Server", "github-repository": "Github Repository", + "only-english": "This part is only available in English.", "patch-url": "https://playdauntless.com/patch-notes/{{version}}", "something-went-wrong": "Something went wrong! Please send the error log below to me in our Discord and try to reload the website. Thank you very much!", "x-twitter": "Dauntless Builder on X.com" @@ -233,7 +234,8 @@ "title": "Create a new build" }, "privacy": { - "title": "Privacy" + "title": "Privacy", + "title-alt": "Privacy Policy for Dauntless Builder" }, "settings": { "community-language": "This language is not officially supported by Dauntless, but members of our community decided to add a translation for Dauntless Builder regardless.", diff --git a/src/pages/about/Privacy.tsx b/src/pages/about/Privacy.tsx index f003f0579..e75aaca69 100644 --- a/src/pages/about/Privacy.tsx +++ b/src/pages/about/Privacy.tsx @@ -1,21 +1,37 @@ -import { Box, Typography } from "@mui/material"; +import { Warning } from "@mui/icons-material"; +import { Alert, Box, useTheme } from "@mui/material"; +import { styled } from "@mui/material/styles"; import PageTitle from "@src/components/PageTitle"; import React from "react"; import { useTranslation } from "react-i18next"; +import privacyContent from "./privacy.html?raw"; + const Privacy: React.FC = () => { const { t } = useTranslation(); + const theme = useTheme(); + + const PrivacyPoliczWrapper = styled("div")` + a { + color: ${theme.palette.primary.main}; + text-decoration: none; + } + `; return ( - + - Lorem ipsum dolor sit amet + } + > + {t("misc.only-english")} + - - - - + +
+ ); }; diff --git a/src/pages/about/privacy.html b/src/pages/about/privacy.html new file mode 100644 index 000000000..805e90ec2 --- /dev/null +++ b/src/pages/about/privacy.html @@ -0,0 +1,193 @@ +

+ At Dauntless Builder, accessible from https://www.dauntless-builder.com, one of our main priorities is the privacy + of our visitors. This Privacy Policy document contains types of information that is collected and recorded by + Dauntless Builder and how we use it. +

+ +

+ If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact + us. +

+ +

+ This Privacy Policy applies only to our online activities and is valid for visitors to our website with regards to + the information that they shared and/or collect in Dauntless Builder. This policy is not applicable to any + information collected offline or via channels other than this website. +

+ +

Consent

+ +

By using our website, you hereby consent to our Privacy Policy and agree to its terms.

+ +

Information we collect

+ +

+ The personal information that you are asked to provide, and the reasons why you are asked to provide it, will be + made clear to you at the point we ask you to provide your personal information. +

+

+ If you contact us directly, we may receive additional information about you such as your name, email address, phone + number, the contents of the message and/or attachments you may send us, and any other information you may choose to + provide. +

+

+ When you register for an Account, we may ask for your contact information, including items such as name, company + name, address, email address, and telephone number. +

+ +

How we use your information

+ +

We use the information we collect in various ways, including to:

+ +
    +
  • Provide, operate, and maintain our website
  • +
  • Improve, personalize, and expand our website
  • +
  • Understand and analyze how you use our website
  • +
  • Develop new products, services, features, and functionality
  • +
  • + Communicate with you, either directly or through one of our partners, including for customer service, to provide + you with updates and other information relating to the website, and for marketing and promotional purposes +
  • +
  • Send you emails
  • +
  • Find and prevent fraud
  • +
+ +

Log Files

+ +

+ Dauntless Builder follows a standard procedure of using log files. These files log visitors when they visit + websites. All hosting companies do this and a part of hosting services' analytics. The information collected by log + files include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp, + referring/exit pages, and possibly the number of clicks. These are not linked to any information that is personally + identifiable. The purpose of the information is for analyzing trends, administering the site, tracking users' + movement on the website, and gathering demographic information. +

+ +

Cookies and Web Beacons

+ +

+ Like any other website, Dauntless Builder uses "cookies". These cookies are used to store information including + visitors' preferences, and the pages on the website that the visitor accessed or visited. The information is used to + optimize the users' experience by customizing our web page content based on visitors' browser type and/or other + information. +

+ +

Our Advertising Partners

+ +

+ Some of advertisers on our site may use cookies and web beacons. Our advertising partners are listed below. Each of + our advertising partners has their own Privacy Policy for their policies on user data. For easier access, we + hyperlinked to their Privacy Policies below. +

+ + + +

Advertising Partners Privacy Policies

+ +

You may consult this list to find the Privacy Policy for each of the advertising partners of Dauntless Builder.

+ +

+ Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web Beacons that are used in + their respective advertisements and links that appear on Dauntless Builder, which are sent directly to users' + browser. They automatically receive your IP address when this occurs. These technologies are used to measure the + effectiveness of their advertising campaigns and/or to personalize the advertising content that you see on websites + that you visit. +

+ +

+ Note that Dauntless Builder has no access to or control over these cookies that are used by third-party advertisers. +

+ +

Third Party Privacy Policies

+ +

+ Dauntless Builder's Privacy Policy does not apply to other advertisers or websites. Thus, we are advising you to + consult the respective Privacy Policies of these third-party ad servers for more detailed information. It may + include their practices and instructions about how to opt-out of certain options. +

+ +

+ You can choose to disable cookies through your individual browser options. To know more detailed information about + cookie management with specific web browsers, it can be found at the browsers' respective websites. +

+ +

CCPA Privacy Rights (Do Not Sell My Personal Information)

+ +

Under the CCPA, among other rights, California consumers have the right to:

+

+ Request that a business that collects a consumer's personal data disclose the categories and specific pieces of + personal data that a business has collected about consumers. +

+

Request that a business delete any personal data about the consumer that a business has collected.

+

Request that a business that sells a consumer's personal data, not sell the consumer's personal data.

+

+ If you make a request, we have one month to respond to you. If you would like to exercise any of these rights, + please contact us. +

+ +

GDPR Data Protection Rights

+ +

+ We would like to make sure you are fully aware of all of your data protection rights. Every user is entitled to the + following: +

+

+ The right to access – You have the right to request copies of your personal data. We may charge you a small fee for + this service. +

+

+ The right to rectification – You have the right to request that we correct any information you believe is + inaccurate. You also have the right to request that we complete the information you believe is incomplete. +

+

The right to erasure – You have the right to request that we erase your personal data, under certain conditions.

+

+ The right to restrict processing – You have the right to request that we restrict the processing of your personal + data, under certain conditions. +

+

+ The right to object to processing – You have the right to object to our processing of your personal data, under + certain conditions. +

+

+ The right to data portability – You have the right to request that we transfer the data that we have collected to + another organization, or directly to you, under certain conditions. +

+

+ If you make a request, we have one month to respond to you. If you would like to exercise any of these rights, + please contact us. +

+ +

Children's Information

+ +

+ Another part of our priority is adding protection for children while using the internet. We encourage parents and + guardians to observe, participate in, and/or monitor and guide their online activity. +

+ +

+ Dauntless Builder does not knowingly collect any Personal Identifiable Information from children under the age of + 13. If you think that your child provided this kind of information on our website, we strongly encourage you to + contact us immediately and we will do our best efforts to promptly remove such information from our records. +

+ +

Changes to This Privacy Policy

+ +

+ We may update our Privacy Policy from time to time. Thus, we advise you to review this page periodically for any + changes. We will notify you of any changes by posting the new Privacy Policy on this page. These changes are + effective immediately, after they are posted on this page. +

+ +

+ Our Privacy Policy was created with the help of the + Privacy Policy Generator. +

+ +

Contact Us

+ +

If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us.

From 7780d8fe348228e6b6083e8070a053afc68679b2 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Fri, 20 Oct 2023 19:33:17 +0900 Subject: [PATCH 06/21] remove now unnecessary tracking option as this will be handled by the CMP --- src/components/Tracking.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/Tracking.tsx b/src/components/Tracking.tsx index c3aa08613..bdc02f0d7 100644 --- a/src/components/Tracking.tsx +++ b/src/components/Tracking.tsx @@ -1,21 +1,12 @@ -import { useAppSelector } from "@src/hooks/redux"; -import { selectConfiguration } from "@src/reducers/configuration/configuration-slice"; import log from "@src/utils/logger"; import ReactGA from "react-ga4"; const Tracking = () => { - const configuration = useAppSelector(selectConfiguration); - // if user has do not track set, don't do anything if (navigator.doNotTrack !== "1") { return null; } - // tracking has not been enabled, skip... - if (!configuration.tracking.enabled) { - return; - } - if (DB_GA4_MEASUREMENT_ID) { ReactGA.initialize(DB_GA4_MEASUREMENT_ID, { testMode: DB_DEVMODE, From 96f0a2dd7aa85b5be460df979a8c69234335c84c Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Sun, 22 Oct 2023 11:59:44 +0900 Subject: [PATCH 07/21] add error boundaries, add pw ids as global vars --- src/@types/globals.d.ts | 6 +++-- src/components/AdSpace.tsx | 3 ++- src/components/AdSpaceFloating.tsx | 3 ++- src/components/BuildMenu.tsx | 3 ++- src/components/Layout.tsx | 6 +++-- src/components/SometingWentWrong.tsx | 34 ++++++++++++++++++++++++++++ src/pages/home/Home.tsx | 2 ++ src/utils/env-tools.ts | 3 +++ vite.config.ts | 30 ++++++++++++++++++++++-- 9 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 src/components/SometingWentWrong.tsx create mode 100644 src/utils/env-tools.ts diff --git a/src/@types/globals.d.ts b/src/@types/globals.d.ts index 081b31259..dbb11b790 100644 --- a/src/@types/globals.d.ts +++ b/src/@types/globals.d.ts @@ -1,4 +1,6 @@ declare const DB_BUILD_TIME: number; +declare const DB_ENABLE_ADS: boolean; declare const DB_DEVMODE: boolean; -declare const DB_GA4_MEASUREMENT_ID: string; -declare const DB_ENABLE_ADS: string; +declare const DB_GA4_MEASUREMENT_ID: string | null; +declare const DB_PW_PUBLISHER_ID: string | null; +declare const DB_PW_WEBSITE_ID: string | null; diff --git a/src/components/AdSpace.tsx b/src/components/AdSpace.tsx index 51c7dd04a..c863f7e8b 100644 --- a/src/components/AdSpace.tsx +++ b/src/components/AdSpace.tsx @@ -1,10 +1,11 @@ import { FeaturedVideo } from "@mui/icons-material"; import { Box, useTheme } from "@mui/material"; +import { adsEnabled } from "@src/utils/env-tools"; const AdSpace = () => { const theme = useTheme(); - if (!DB_ENABLE_ADS) { + if (!adsEnabled()) { return null; } diff --git a/src/components/AdSpaceFloating.tsx b/src/components/AdSpaceFloating.tsx index 330e842d4..fd4ece5dd 100644 --- a/src/components/AdSpaceFloating.tsx +++ b/src/components/AdSpaceFloating.tsx @@ -2,6 +2,7 @@ import { Box, useTheme } from "@mui/material"; import AdSpace from "@src/components/AdSpace"; import useIsMobile from "@src/hooks/is-mobile"; import useWindowSize from "@src/hooks/window-size"; +import { adsEnabled } from "@src/utils/env-tools"; import React from "react"; export const adSpaceRightSideMinSize = 300; @@ -12,7 +13,7 @@ const AdSpaceFloating = () => { const isMobile = useIsMobile(); const { width } = useWindowSize(); - if (!DB_ENABLE_ADS) { + if (!adsEnabled()) { return null; } diff --git a/src/components/BuildMenu.tsx b/src/components/BuildMenu.tsx index 692d2a3f9..daa1ed671 100644 --- a/src/components/BuildMenu.tsx +++ b/src/components/BuildMenu.tsx @@ -14,6 +14,7 @@ import { import { buildIdRegex } from "@src/utils/build-id"; import { defaultBuildName } from "@src/utils/default-build-name"; import { useAtomValue, useSetAtom } from "jotai"; +import { adsEnabled } from "@src/utils/env-tools"; import { useSnackbar } from "notistack"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -94,7 +95,7 @@ const BuildMenu: React.FC = () => { color="primary" onClick={handleCopyToClipboardClicked} sx={{ - bottom: DB_ENABLE_ADS ? `${adSpaceMobileBannerHeight}px` : theme.spacing(2), + bottom: adsEnabled() ? `${adSpaceMobileBannerHeight}px` : theme.spacing(2), position: "fixed", right: theme.spacing(3), }} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index a3618d5cc..434ce0841 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -35,6 +35,7 @@ import AdSpace from "@src/components/AdSpace"; import AdSpaceFloating, { adSpaceRightSideMinSize } from "@src/components/AdSpaceFloating"; import BuildMenu from "@src/components/BuildMenu"; import LinkBox from "@src/components/LinkBox"; +import SomethingWentWrong from "@src/components/SometingWentWrong"; import Spacer from "@src/components/Spacer"; import { drawerWidth } from "@src/components/theme"; import { crowdinLink, discordServerUrl, githubUrl, xTwitterUrl } from "@src/constants"; @@ -45,6 +46,7 @@ import useIsMobile from "@src/hooks/is-mobile"; import { currentLanguage, getNativeLanguageName, isBetaLanguage, Language } from "@src/i18n"; import { favoritesView } from "@src/state/favorites"; import useWindowSize from "@src/hooks/window-size"; +import { adsEnabled } from "@src/utils/env-tools"; import log from "@src/utils/logger"; import { useAtomValue } from "jotai"; import React, { ReactNode, useState } from "react"; @@ -56,7 +58,6 @@ import { NavLink } from "react-router-dom"; import { AppBar } from "./AppBar"; import { DrawerHeader } from "./Drawer"; -import SomethingWentWrong from "./SomethingWentWrong"; interface LayoutProps { children: ReactNode; @@ -87,7 +88,7 @@ const Layout: React.FC = ({ children }) => { { icon: , link: "/settings", text: t("drawer.settings") }, ]; - const showLeftSideAdSpace = DB_ENABLE_ADS && (width - theme.breakpoints.values.xl) * 0.5 <= adSpaceRightSideMinSize; + const showLeftSideAdSpace = adsEnabled() && (width - theme.breakpoints.values.xl) * 0.5 <= adSpaceRightSideMinSize; return ( @@ -242,6 +243,7 @@ const Layout: React.FC = ({ children }) => { + diff --git a/src/components/SometingWentWrong.tsx b/src/components/SometingWentWrong.tsx new file mode 100644 index 000000000..1eeef5989 --- /dev/null +++ b/src/components/SometingWentWrong.tsx @@ -0,0 +1,34 @@ +import { Download } from "@mui/icons-material"; +import { + Alert, + Box, + Button, + Card, +} from "@mui/material"; +import { downloadJsonObject } from "@src/utils/download-json"; +import log, { Logger } from "@src/utils/logger"; +import React from "react"; +import { FallbackProps } from "react-error-boundary/dist/declarations/src/types"; +import { useTranslation } from "react-i18next"; + +const SomethingWentWrong = ({ error }: FallbackProps) => { + const { t } = useTranslation(); + + log.error(error.message); + + return ( + + {t("misc.something-went-wrong")} + {error.message} + + + ); +}; + +export default React.memo(SomethingWentWrong); diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 958cb688a..c8f7b9c1c 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -27,6 +27,8 @@ const Home: React.FC = () => { const favorites = useAtomValue(favoritesView); const isMobile = useIsMobile(); + throw Error("Yolo"); + return ( { + return DB_ENABLE_ADS && DB_PW_PUBLISHER_ID !== null && DB_PW_WEBSITE_ID !== null; +}; diff --git a/vite.config.ts b/vite.config.ts index 168ae92ca..f34fd335a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,6 +8,30 @@ import { VitePWA } from "vite-plugin-pwa"; const maximumFileSizeToCacheInBytes = 5 * 1024 * 1024; // 5 MB +const env = (key: string, type: "string"|"bool" = "string") : string|null|boolean => { + let val = process.env[key]; + + if (type === "string") { + if (!val) { + return null; + } + + return JSON.stringify(val); + } + + if (type === "bool") { + if (!val) { + return false; + } + + val = val.toLowerCase(); + + return val === "true" || val === "1" || val === "on" || val === "yes"; + } + + throw Error("Invalid argument for type"); +}; + export default defineConfig(({ command, mode }) => { const isDevMode = mode === "development"; @@ -22,8 +46,10 @@ export default defineConfig(({ command, mode }) => { define: { DB_BUILD_TIME: Date.now(), DB_DEVMODE: isDevMode, - DB_ENABLE_ADS: process.env["DB_ENABLE_ADS"] === "true", - DB_GA4_MEASUREMENT_ID: JSON.stringify(process.env["DB_GA4_MEASUREMENT_ID"]), + DB_ENABLE_ADS: env("DB_ENABLE_ADS", "bool"), + DB_GA4_MEASUREMENT_ID:env("DB_GA4_MEASUREMENT_ID"), + DB_PW_PUBLISHER_ID: env("DB_PW_PUBLISHER_ID"), + DB_PW_WEBSITE_ID: env("DB_PW_WEBSITE_ID"), }, plugins: [ react({ babel: { plugins: [jotaiDebugLabel, jotaiReactRefresh] } }), From bf83c4e5afee67d96610013af8b4108f0a10e900 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Sun, 22 Oct 2023 12:00:27 +0900 Subject: [PATCH 08/21] remove throw from home page --- src/components/SometingWentWrong.tsx | 7 +------ src/pages/home/Home.tsx | 2 -- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/components/SometingWentWrong.tsx b/src/components/SometingWentWrong.tsx index 1eeef5989..7389bd79a 100644 --- a/src/components/SometingWentWrong.tsx +++ b/src/components/SometingWentWrong.tsx @@ -1,10 +1,5 @@ import { Download } from "@mui/icons-material"; -import { - Alert, - Box, - Button, - Card, -} from "@mui/material"; +import { Alert, Box, Button, Card } from "@mui/material"; import { downloadJsonObject } from "@src/utils/download-json"; import log, { Logger } from "@src/utils/logger"; import React from "react"; diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index c8f7b9c1c..958cb688a 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -27,8 +27,6 @@ const Home: React.FC = () => { const favorites = useAtomValue(favoritesView); const isMobile = useIsMobile(); - throw Error("Yolo"); - return ( Date: Sun, 22 Oct 2023 13:44:09 +0900 Subject: [PATCH 09/21] add pw units --- src/@types/playwire.d.ts | 17 ++++++ src/app.tsx | 6 +-- src/components/AdSpace.tsx | 48 +++++++++++++++-- src/components/AdSpaceFloating.tsx | 6 +-- src/components/BuildMenu.tsx | 2 +- src/components/Layout.tsx | 10 ++-- src/components/Tracking.tsx | 20 -------- src/components/TrackingRampSetup.tsx | 77 ++++++++++++++++++++++++++++ src/reducers/events/events-slice.ts | 28 ++++++++++ src/utils/env-tools.ts | 4 +- 10 files changed, 178 insertions(+), 40 deletions(-) create mode 100644 src/@types/playwire.d.ts delete mode 100644 src/components/Tracking.tsx create mode 100644 src/components/TrackingRampSetup.tsx create mode 100644 src/reducers/events/events-slice.ts diff --git a/src/@types/playwire.d.ts b/src/@types/playwire.d.ts new file mode 100644 index 000000000..680f0b74d --- /dev/null +++ b/src/@types/playwire.d.ts @@ -0,0 +1,17 @@ +interface PlaywireUnit { + type: string; + selectorId?: string; +} + +interface Window { + ramp: { + que: unknown[]; + passiveMode: boolean; + addUnits: (units: PlaywireUnit[]) => Promise; + displayUnits: () => void; + }; + + _pwGA4PageviewId: string; + dataLayer: unknown[]; + gtag: (...args: unknown[]) => void; +} diff --git a/src/app.tsx b/src/app.tsx index 3a6e5c16a..df8486ea2 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -22,10 +22,6 @@ import BackgroundTasks from "@src/components/BackgroundTasks"; import Favorites from "@src/pages/favorites/Favorites"; import useIsMobile from "@src/hooks/is-mobile"; import log from "@src/utils/logger"; -import { useAppSelector } from "@src/hooks/redux"; -import { selectConfiguration } from "@src/reducers/configuration/configuration-slice"; -import SomethingWentWrong from "@src/components/SomethingWentWrong"; -import { ErrorBoundary } from "react-error-boundary"; import useIsLightMode from "@src/hooks/light-mode"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { isRtlLanguage, Language } from "@src/i18n"; @@ -35,6 +31,8 @@ import rtlPlugin from "stylis-plugin-rtl"; import { prefixer } from "stylis"; import { CacheProvider } from "@emotion/react"; import createCache from "@emotion/cache"; +import { ErrorBoundary } from "react-error-boundary"; +import SomethingWentWrong from "@src/components/SometingWentWrong"; const DauntlessBuilderApp = () => { const isMobile = useIsMobile(); diff --git a/src/components/AdSpace.tsx b/src/components/AdSpace.tsx index c863f7e8b..66794dd48 100644 --- a/src/components/AdSpace.tsx +++ b/src/components/AdSpace.tsx @@ -1,11 +1,51 @@ import { FeaturedVideo } from "@mui/icons-material"; import { Box, useTheme } from "@mui/material"; +import { useAppSelector } from "@src/hooks/redux"; +import { selectEvents } from "@src/reducers/events/events-slice"; import { adsEnabled } from "@src/utils/env-tools"; +import log from "@src/utils/logger"; +import React, { useEffect } from "react"; -const AdSpace = () => { +export enum UnitType { + RightRail = "right_rail", + BottomRail = "bottom_rail", + LeftRail = "left_rail", +} + +interface AdSpaceProps { + unitType: UnitType; +} + +const AdSpace: React.FC = ({ unitType }) => { const theme = useTheme(); - if (!adsEnabled()) { + const events = useAppSelector(selectEvents); + + const selectorName = `db_unit_${unitType.toString()}`; + + useEffect(() => { + if (!events.playwireSetupHasFinished) { + return; + } + + const initUnit = async () => { + try { + await window.ramp.addUnits([ + { + selectorId: selectorName, + type: unitType, + }, + ]); + window.ramp.displayUnits(); + } catch (error) { + log.error("ramp: could not add unit", { error }); + window.ramp.displayUnits(); + } + }; + initUnit(); + }, [events.playwireSetupHasFinished, selectorName, unitType]); + + if (!adsEnabled) { return null; } @@ -28,7 +68,7 @@ const AdSpace = () => { ); } - return null; + return
; }; -export default AdSpace; +export default React.memo(AdSpace); diff --git a/src/components/AdSpaceFloating.tsx b/src/components/AdSpaceFloating.tsx index fd4ece5dd..48cff70ba 100644 --- a/src/components/AdSpaceFloating.tsx +++ b/src/components/AdSpaceFloating.tsx @@ -1,5 +1,5 @@ import { Box, useTheme } from "@mui/material"; -import AdSpace from "@src/components/AdSpace"; +import AdSpace, { UnitType } from "@src/components/AdSpace"; import useIsMobile from "@src/hooks/is-mobile"; import useWindowSize from "@src/hooks/window-size"; import { adsEnabled } from "@src/utils/env-tools"; @@ -13,7 +13,7 @@ const AdSpaceFloating = () => { const isMobile = useIsMobile(); const { width } = useWindowSize(); - if (!adsEnabled()) { + if (!adsEnabled) { return null; } @@ -49,7 +49,7 @@ const AdSpaceFloating = () => { return ( - + ); }; diff --git a/src/components/BuildMenu.tsx b/src/components/BuildMenu.tsx index daa1ed671..10924577c 100644 --- a/src/components/BuildMenu.tsx +++ b/src/components/BuildMenu.tsx @@ -95,7 +95,7 @@ const BuildMenu: React.FC = () => { color="primary" onClick={handleCopyToClipboardClicked} sx={{ - bottom: adsEnabled() ? `${adSpaceMobileBannerHeight}px` : theme.spacing(2), + bottom: adsEnabled ? `${adSpaceMobileBannerHeight}px` : theme.spacing(2), position: "fixed", right: theme.spacing(3), }} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 434ce0841..672e95902 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -31,15 +31,15 @@ import { Typography, useTheme, } from "@mui/material"; -import AdSpace from "@src/components/AdSpace"; +import AdSpace, { UnitType } from "@src/components/AdSpace"; import AdSpaceFloating, { adSpaceRightSideMinSize } from "@src/components/AdSpaceFloating"; import BuildMenu from "@src/components/BuildMenu"; import LinkBox from "@src/components/LinkBox"; import SomethingWentWrong from "@src/components/SometingWentWrong"; import Spacer from "@src/components/Spacer"; import { drawerWidth } from "@src/components/theme"; +import TrackingRampSetup from "@src/components/TrackingRampSetup"; import { crowdinLink, discordServerUrl, githubUrl, xTwitterUrl } from "@src/constants"; -import Tracking from "@src/components/Tracking"; import dauntlessBuilderData from "@src/data/Data"; import useDevMode from "@src/hooks/dev-mode"; import useIsMobile from "@src/hooks/is-mobile"; @@ -88,7 +88,7 @@ const Layout: React.FC = ({ children }) => { { icon: , link: "/settings", text: t("drawer.settings") }, ]; - const showLeftSideAdSpace = adsEnabled() && (width - theme.breakpoints.values.xl) * 0.5 <= adSpaceRightSideMinSize; + const showLeftSideAdSpace = adsEnabled && (width - theme.breakpoints.values.xl) * 0.5 <= adSpaceRightSideMinSize; return ( @@ -190,7 +190,7 @@ const Layout: React.FC = ({ children }) => { ))} - {isMobile ? : showLeftSideAdSpace ? : } + {isMobile ? : showLeftSideAdSpace ? : } = ({ children }) => { - + ); diff --git a/src/components/Tracking.tsx b/src/components/Tracking.tsx deleted file mode 100644 index bdc02f0d7..000000000 --- a/src/components/Tracking.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import log from "@src/utils/logger"; -import ReactGA from "react-ga4"; - -const Tracking = () => { - // if user has do not track set, don't do anything - if (navigator.doNotTrack !== "1") { - return null; - } - - if (DB_GA4_MEASUREMENT_ID) { - ReactGA.initialize(DB_GA4_MEASUREMENT_ID, { - testMode: DB_DEVMODE, - }); - log.debug("enabled GA4"); - } - - return null; -}; - -export default Tracking; diff --git a/src/components/TrackingRampSetup.tsx b/src/components/TrackingRampSetup.tsx new file mode 100644 index 000000000..85bdf7bbc --- /dev/null +++ b/src/components/TrackingRampSetup.tsx @@ -0,0 +1,77 @@ +import { UnitType } from "@src/components/AdSpace"; +import { useAppDispatch } from "@src/hooks/redux"; +import { playwireSetupFinished } from "@src/reducers/events/events-slice"; +import { adsEnabled } from "@src/utils/env-tools"; +import log from "@src/utils/logger"; +import React, { useEffect } from "react"; +import ReactGA from "react-ga4"; + +const TrackingRampSetup = () => { + const dispatch = useAppDispatch(); + + useEffect(() => { + const ga4Enabled = navigator.doNotTrack !== "1" && DB_GA4_MEASUREMENT_ID !== null; + + if (ga4Enabled) { + ReactGA.initialize(DB_GA4_MEASUREMENT_ID, { + testMode: DB_DEVMODE, + }); + log.debug("enabled GA4"); + } + + if (adsEnabled) { + if (window.ramp) { + return; + } + + window.ramp = window.ramp ?? {}; + window.ramp.que = window.ramp.que ?? []; + window.ramp.passiveMode = true; + + if (ga4Enabled) { + const gtagFunc = (...args: unknown[]) => window.dataLayer.push(args); + + window._pwGA4PageviewId = Date.now().toString(); + window.dataLayer = window.dataLayer || []; + window.gtag = window.gtag || gtagFunc; + window.gtag("js", new Date()); + window.gtag("config", DB_GA4_MEASUREMENT_ID, { send_page_view: false }); + window.gtag("event", "ramp_js", { + pageview_id: window._pwGA4PageviewId, + send_to: DB_GA4_MEASUREMENT_ID, + }); + } + + window.ramp.que.push(async () => { + try { + await window.ramp.addUnits([ + { + type: UnitType.RightRail, + }, + { + type: UnitType.BottomRail, + }, + { + type: UnitType.LeftRail, + }, + ]); + window.ramp.displayUnits(); + } catch (error) { + log.error("ramp: could not add units", { error }); + window.ramp.displayUnits(); + } + + dispatch(playwireSetupFinished()); + }); + + const rampScript = document.createElement("script"); + rampScript.src = `https://cdn.intergient.com/${DB_PW_PUBLISHER_ID}/${DB_PW_WEBSITE_ID}/ramp.js`; + rampScript.async = true; + document.body.appendChild(rampScript); + } + }, [dispatch]); + + return null; +}; + +export default React.memo(TrackingRampSetup); diff --git a/src/reducers/events/events-slice.ts b/src/reducers/events/events-slice.ts new file mode 100644 index 000000000..4f933425d --- /dev/null +++ b/src/reducers/events/events-slice.ts @@ -0,0 +1,28 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { RootState } from "@src/store"; + +export interface EventsState { + playwireSetupHasFinished: boolean; +} + +const initialState: EventsState = { + playwireSetupHasFinished: false, +}; + +export const eventsSlice = createSlice({ + initialState, + name: "events", + reducers: { + playwireSetupFinished: state => { + state.playwireSetupHasFinished = true; + }, + }, +}); + +const initState = (state: EventsState) => Object.assign({}, initialState, state); + +export const selectEvents = (state: RootState) => initState(state.events); + +export const { playwireSetupFinished } = eventsSlice.actions; + +export default eventsSlice.reducer; diff --git a/src/utils/env-tools.ts b/src/utils/env-tools.ts index 7df66b151..09099bd9b 100644 --- a/src/utils/env-tools.ts +++ b/src/utils/env-tools.ts @@ -1,3 +1 @@ -export const adsEnabled = (): boolean => { - return DB_ENABLE_ADS && DB_PW_PUBLISHER_ID !== null && DB_PW_WEBSITE_ID !== null; -}; +export const adsEnabled = DB_ENABLE_ADS && DB_PW_PUBLISHER_ID !== null && DB_PW_WEBSITE_ID !== null; From f3ec7c52a6b24c0ab3875ab3731baf0f72b377c2 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Mon, 23 Oct 2023 15:40:47 +0900 Subject: [PATCH 10/21] minor performance improvements by caching some selectors --- src/components/BackgroundTasks.tsx | 1 + src/components/Layout.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/BackgroundTasks.tsx b/src/components/BackgroundTasks.tsx index c8d93e273..19c370aee 100644 --- a/src/components/BackgroundTasks.tsx +++ b/src/components/BackgroundTasks.tsx @@ -7,6 +7,7 @@ import { addFavorite, favoritesAtom, favoritesView, isBuildInFavorites } from "@ import log, { LogLevel } from "@src/utils/logger"; import { useAtom, useAtomValue, useSetAtom } from "jotai"; import React, { useEffect } from "react"; +import useDevMode from "@src/hooks/dev-mode"; const BackgroundTasks: React.FC = () => { const favorites = useAtomValue(favoritesView); diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 672e95902..6e664c340 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -58,6 +58,7 @@ import { NavLink } from "react-router-dom"; import { AppBar } from "./AppBar"; import { DrawerHeader } from "./Drawer"; +import useDevMode from "@src/hooks/dev-mode"; interface LayoutProps { children: ReactNode; From 3f89fc7a802ddeecc3ddb7fda1c8533b52e7b0f5 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Mon, 23 Oct 2023 19:56:05 +0900 Subject: [PATCH 11/21] fix some rebase shenanings --- src/components/BackgroundTasks.tsx | 1 - src/components/Layout.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/BackgroundTasks.tsx b/src/components/BackgroundTasks.tsx index 19c370aee..c8d93e273 100644 --- a/src/components/BackgroundTasks.tsx +++ b/src/components/BackgroundTasks.tsx @@ -7,7 +7,6 @@ import { addFavorite, favoritesAtom, favoritesView, isBuildInFavorites } from "@ import log, { LogLevel } from "@src/utils/logger"; import { useAtom, useAtomValue, useSetAtom } from "jotai"; import React, { useEffect } from "react"; -import useDevMode from "@src/hooks/dev-mode"; const BackgroundTasks: React.FC = () => { const favorites = useAtomValue(favoritesView); diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 6e664c340..672e95902 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -58,7 +58,6 @@ import { NavLink } from "react-router-dom"; import { AppBar } from "./AppBar"; import { DrawerHeader } from "./Drawer"; -import useDevMode from "@src/hooks/dev-mode"; interface LayoutProps { children: ReactNode; From 96d09d7d95d6b6a58a92443c85d62783fd98e9a7 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Sun, 5 Nov 2023 14:08:43 +0900 Subject: [PATCH 12/21] finish rebasing --- src/app.tsx | 6 ++++++ src/components/AdSpace.tsx | 6 +++--- src/components/BuildMenu.tsx | 2 +- src/components/Layout.tsx | 2 +- src/components/TrackingRampSetup.tsx | 10 +++++----- src/reducers/events/events-slice.ts | 28 ---------------------------- src/state/events.ts | 11 +++++++++++ 7 files changed, 27 insertions(+), 38 deletions(-) delete mode 100644 src/reducers/events/events-slice.ts create mode 100644 src/state/events.ts diff --git a/src/app.tsx b/src/app.tsx index df8486ea2..d62a4a91a 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -33,6 +33,7 @@ import { CacheProvider } from "@emotion/react"; import createCache from "@emotion/cache"; import { ErrorBoundary } from "react-error-boundary"; import SomethingWentWrong from "@src/components/SometingWentWrong"; +import Privacy from "./pages/about/Privacy"; const DauntlessBuilderApp = () => { const isMobile = useIsMobile(); @@ -135,6 +136,11 @@ const DauntlessBuilderApp = () => { path="/about" /> + } + path="/privacy" + /> + } path="/settings" diff --git a/src/components/AdSpace.tsx b/src/components/AdSpace.tsx index 66794dd48..5d55db7c7 100644 --- a/src/components/AdSpace.tsx +++ b/src/components/AdSpace.tsx @@ -1,9 +1,9 @@ import { FeaturedVideo } from "@mui/icons-material"; import { Box, useTheme } from "@mui/material"; -import { useAppSelector } from "@src/hooks/redux"; -import { selectEvents } from "@src/reducers/events/events-slice"; +import { eventsAtom } from "@src/state/events"; import { adsEnabled } from "@src/utils/env-tools"; import log from "@src/utils/logger"; +import { useAtomValue } from "jotai"; import React, { useEffect } from "react"; export enum UnitType { @@ -19,7 +19,7 @@ interface AdSpaceProps { const AdSpace: React.FC = ({ unitType }) => { const theme = useTheme(); - const events = useAppSelector(selectEvents); + const events = useAtomValue(eventsAtom); const selectorName = `db_unit_${unitType.toString()}`; diff --git a/src/components/BuildMenu.tsx b/src/components/BuildMenu.tsx index 10924577c..1330c94d2 100644 --- a/src/components/BuildMenu.tsx +++ b/src/components/BuildMenu.tsx @@ -13,8 +13,8 @@ import { } from "@src/state/favorites"; import { buildIdRegex } from "@src/utils/build-id"; import { defaultBuildName } from "@src/utils/default-build-name"; -import { useAtomValue, useSetAtom } from "jotai"; import { adsEnabled } from "@src/utils/env-tools"; +import { useAtomValue, useSetAtom } from "jotai"; import { useSnackbar } from "notistack"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 672e95902..8458a6c7e 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -43,9 +43,9 @@ import { crowdinLink, discordServerUrl, githubUrl, xTwitterUrl } from "@src/cons import dauntlessBuilderData from "@src/data/Data"; import useDevMode from "@src/hooks/dev-mode"; import useIsMobile from "@src/hooks/is-mobile"; +import useWindowSize from "@src/hooks/window-size"; import { currentLanguage, getNativeLanguageName, isBetaLanguage, Language } from "@src/i18n"; import { favoritesView } from "@src/state/favorites"; -import useWindowSize from "@src/hooks/window-size"; import { adsEnabled } from "@src/utils/env-tools"; import log from "@src/utils/logger"; import { useAtomValue } from "jotai"; diff --git a/src/components/TrackingRampSetup.tsx b/src/components/TrackingRampSetup.tsx index 85bdf7bbc..c969625ef 100644 --- a/src/components/TrackingRampSetup.tsx +++ b/src/components/TrackingRampSetup.tsx @@ -1,13 +1,13 @@ import { UnitType } from "@src/components/AdSpace"; -import { useAppDispatch } from "@src/hooks/redux"; -import { playwireSetupFinished } from "@src/reducers/events/events-slice"; +import { eventsAtom, playwireSetupHasFinished } from "@src/state/events"; import { adsEnabled } from "@src/utils/env-tools"; import log from "@src/utils/logger"; +import { useSetAtom } from "jotai"; import React, { useEffect } from "react"; import ReactGA from "react-ga4"; const TrackingRampSetup = () => { - const dispatch = useAppDispatch(); + const setEvents = useSetAtom(eventsAtom); useEffect(() => { const ga4Enabled = navigator.doNotTrack !== "1" && DB_GA4_MEASUREMENT_ID !== null; @@ -61,7 +61,7 @@ const TrackingRampSetup = () => { window.ramp.displayUnits(); } - dispatch(playwireSetupFinished()); + setEvents(playwireSetupHasFinished()); }); const rampScript = document.createElement("script"); @@ -69,7 +69,7 @@ const TrackingRampSetup = () => { rampScript.async = true; document.body.appendChild(rampScript); } - }, [dispatch]); + }, [setEvents]); return null; }; diff --git a/src/reducers/events/events-slice.ts b/src/reducers/events/events-slice.ts deleted file mode 100644 index 4f933425d..000000000 --- a/src/reducers/events/events-slice.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { RootState } from "@src/store"; - -export interface EventsState { - playwireSetupHasFinished: boolean; -} - -const initialState: EventsState = { - playwireSetupHasFinished: false, -}; - -export const eventsSlice = createSlice({ - initialState, - name: "events", - reducers: { - playwireSetupFinished: state => { - state.playwireSetupHasFinished = true; - }, - }, -}); - -const initState = (state: EventsState) => Object.assign({}, initialState, state); - -export const selectEvents = (state: RootState) => initState(state.events); - -export const { playwireSetupFinished } = eventsSlice.actions; - -export default eventsSlice.reducer; diff --git a/src/state/events.ts b/src/state/events.ts new file mode 100644 index 000000000..aac80b746 --- /dev/null +++ b/src/state/events.ts @@ -0,0 +1,11 @@ +import { atom } from "jotai"; + +export interface EventsState { + playwireSetupHasFinished: boolean; +} + +export const eventsAtom = atom({ + playwireSetupHasFinished: false, +}); + +export const playwireSetupHasFinished = () => (state: EventsState) => ({ ...state, playwireSetupHasFinished: true }); From 602474d75b3e4fabc1601a0c37dbf8d16b48ce2d Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Sun, 5 Nov 2023 14:27:28 +0900 Subject: [PATCH 13/21] update privacy policy --- src/pages/about/privacy.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/pages/about/privacy.html b/src/pages/about/privacy.html index 805e90ec2..74494e10a 100644 --- a/src/pages/about/privacy.html +++ b/src/pages/about/privacy.html @@ -87,6 +87,20 @@

Our Advertising Partners

+

Playwire LLC

+ +

All or partial advertising on this Website or App is managed by Playwire LLC. If Playwire publisher advertising + services are used, Playwire LLC may collect and use certain aggregated and anonymized data for advertising purposes. + To learn more about the types of data collected, how data is used and your choices as a user, please visit https://www.playwire.com/privacy-policy.

+

For EU Users only: If you are located in countries that are part of the European Economic Area, in + the United Kingdom or Switzerland, and publisher advertising services are being provided by Playwire LLC, you were + presented with messaging from our Consent Management Platform (CMP) around your privacy choices as a user in regards + to digital advertising, applicable vendors, cookie usage and more. If you’d like to revisit the choices you have + made previously on this Website or App, please click here.

+ +

Advertising Partners Privacy Policies

You may consult this list to find the Privacy Policy for each of the advertising partners of Dauntless Builder.

From 28caa9374287041002021de2a7f6bd87eba6cba9 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Sun, 5 Nov 2023 14:33:34 +0900 Subject: [PATCH 14/21] remove CSP for testing --- .netlify/config/_headers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.netlify/config/_headers b/.netlify/config/_headers index 8e3b576fe..a2d6e0272 100644 --- a/.netlify/config/_headers +++ b/.netlify/config/_headers @@ -2,5 +2,5 @@ X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff - Content-Security-Policy: default-src https: 'unsafe-inline' + Referrer-Policy: no-referrer-when-downgrade Feature-Policy: accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment 'none'; usb 'none' From 2af52b268a005225aa218d5aed8bcc40061c9844 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Mon, 6 Nov 2023 10:29:10 +0900 Subject: [PATCH 15/21] improve ramp setup --- src/app.tsx | 2 ++ src/components/AdSpace.tsx | 11 ++++++++--- src/components/AdSpaceFloating.tsx | 2 +- src/components/Layout.tsx | 9 +-------- src/components/TrackingRampSetup.tsx | 21 ++------------------- 5 files changed, 14 insertions(+), 31 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index d62a4a91a..fa4c6d2f1 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -34,6 +34,7 @@ import createCache from "@emotion/cache"; import { ErrorBoundary } from "react-error-boundary"; import SomethingWentWrong from "@src/components/SometingWentWrong"; import Privacy from "./pages/about/Privacy"; +import TrackingRampSetup from "@src/components/TrackingRampSetup"; const DauntlessBuilderApp = () => { const isMobile = useIsMobile(); @@ -65,6 +66,7 @@ const DauntlessBuilderApp = () => { FallbackComponent={SomethingWentWrong} onError={(e, info) => log.error(e.message, { info })} > + = ({ unitType }) => { +const AdSpace: React.FC = ({ name, unitType }) => { const theme = useTheme(); + const isDevMode = useDevMode(); const events = useAtomValue(eventsAtom); - const selectorName = `db_unit_${unitType.toString()}`; + const selectorName = `dbu_${md5(name + unitType.toString())}`; useEffect(() => { if (!events.playwireSetupHasFinished) { @@ -49,7 +54,7 @@ const AdSpace: React.FC = ({ unitType }) => { return null; } - if (DB_DEVMODE) { + if (isDevMode) { return ( { return ( - + ); }; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 8458a6c7e..86036ae38 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -38,15 +38,12 @@ import LinkBox from "@src/components/LinkBox"; import SomethingWentWrong from "@src/components/SometingWentWrong"; import Spacer from "@src/components/Spacer"; import { drawerWidth } from "@src/components/theme"; -import TrackingRampSetup from "@src/components/TrackingRampSetup"; import { crowdinLink, discordServerUrl, githubUrl, xTwitterUrl } from "@src/constants"; import dauntlessBuilderData from "@src/data/Data"; import useDevMode from "@src/hooks/dev-mode"; import useIsMobile from "@src/hooks/is-mobile"; -import useWindowSize from "@src/hooks/window-size"; import { currentLanguage, getNativeLanguageName, isBetaLanguage, Language } from "@src/i18n"; import { favoritesView } from "@src/state/favorites"; -import { adsEnabled } from "@src/utils/env-tools"; import log from "@src/utils/logger"; import { useAtomValue } from "jotai"; import React, { ReactNode, useState } from "react"; @@ -67,7 +64,6 @@ const Layout: React.FC = ({ children }) => { const theme = useTheme(); const isMobile = useIsMobile(); - const { width } = useWindowSize(); const [open, setOpen] = useState(false); const { t } = useTranslation(); const devMode = useDevMode(); @@ -88,8 +84,6 @@ const Layout: React.FC = ({ children }) => { { icon: , link: "/settings", text: t("drawer.settings") }, ]; - const showLeftSideAdSpace = adsEnabled && (width - theme.breakpoints.values.xl) * 0.5 <= adSpaceRightSideMinSize; - return ( @@ -190,7 +184,7 @@ const Layout: React.FC = ({ children }) => { ))} - {isMobile ? : showLeftSideAdSpace ? : } + = ({ children }) => { - ); diff --git a/src/components/TrackingRampSetup.tsx b/src/components/TrackingRampSetup.tsx index c969625ef..521e8370c 100644 --- a/src/components/TrackingRampSetup.tsx +++ b/src/components/TrackingRampSetup.tsx @@ -42,25 +42,8 @@ const TrackingRampSetup = () => { }); } - window.ramp.que.push(async () => { - try { - await window.ramp.addUnits([ - { - type: UnitType.RightRail, - }, - { - type: UnitType.BottomRail, - }, - { - type: UnitType.LeftRail, - }, - ]); - window.ramp.displayUnits(); - } catch (error) { - log.error("ramp: could not add units", { error }); - window.ramp.displayUnits(); - } - + window.ramp.que.push(() => { + log.debug("playwire has been setup") setEvents(playwireSetupHasFinished()); }); From fda8ac95889dd74dfd175665bbd846ec83f47b42 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Sat, 11 Nov 2023 19:41:22 +0900 Subject: [PATCH 16/21] improve pw unit logic --- src/@types/globals.d.ts | 1 + src/@types/playwire.d.ts | 1 + src/components/AdSpace.tsx | 120 ++++++++++++++++++++------- src/components/AdSpaceFloating.tsx | 79 ++++++++++-------- src/components/BuildMenu.tsx | 15 +++- src/components/Layout.tsx | 31 ++++++- src/components/TrackingRampSetup.tsx | 26 +++++- src/constants.ts | 3 + src/json/i18n/en/en.json | 1 + src/pages/about/privacy.html | 30 ++++--- src/state/events.ts | 9 ++ vite.config.ts | 1 + 12 files changed, 228 insertions(+), 89 deletions(-) diff --git a/src/@types/globals.d.ts b/src/@types/globals.d.ts index dbb11b790..e1cad133f 100644 --- a/src/@types/globals.d.ts +++ b/src/@types/globals.d.ts @@ -1,6 +1,7 @@ declare const DB_BUILD_TIME: number; declare const DB_ENABLE_ADS: boolean; declare const DB_DEVMODE: boolean; +declare const DB_DISPLAY_AD_PLACEHOLDERS: boolean; declare const DB_GA4_MEASUREMENT_ID: string | null; declare const DB_PW_PUBLISHER_ID: string | null; declare const DB_PW_WEBSITE_ID: string | null; diff --git a/src/@types/playwire.d.ts b/src/@types/playwire.d.ts index 680f0b74d..365cb73d7 100644 --- a/src/@types/playwire.d.ts +++ b/src/@types/playwire.d.ts @@ -9,6 +9,7 @@ interface Window { passiveMode: boolean; addUnits: (units: PlaywireUnit[]) => Promise; displayUnits: () => void; + destroyUnits: (what: string) => void; }; _pwGA4PageviewId: string; diff --git a/src/components/AdSpace.tsx b/src/components/AdSpace.tsx index a47c5ed7d..37b3cbbd6 100644 --- a/src/components/AdSpace.tsx +++ b/src/components/AdSpace.tsx @@ -1,34 +1,78 @@ import { FeaturedVideo } from "@mui/icons-material"; -import { Box, useTheme } from "@mui/material"; -import { eventsAtom } from "@src/state/events"; +import { Box, ListSubheader, useTheme } from "@mui/material"; +import { eventsAtom, playwireAddInitializedUnit } from "@src/state/events"; import { adsEnabled } from "@src/utils/env-tools"; import log from "@src/utils/logger"; -import { useAtomValue } from "jotai"; -import React, { useEffect } from "react"; -import useDevMode from "@src/hooks/dev-mode"; +import { useAtom } from "jotai"; import md5 from "md5"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; export enum UnitType { - RightRail = "right_rail", BottomRail = "bottom_rail", - LeftRail = "left_rail", - Skyscraper = "sky_atf", + Skyscraper160x600 = "sky_atf", + Skyscraper300x600 = "sky_btf", } +export const adSpaceSize = { + [UnitType.BottomRail]: { height: 50, width: 320 }, + [UnitType.Skyscraper160x600]: { height: 600, width: 160 }, + [UnitType.Skyscraper300x600]: { height: 600, width: 300 }, +}; + interface AdSpaceProps { name: string; unitType: UnitType; + withoutHeader?: boolean; } -const AdSpace: React.FC = ({ name, unitType }) => { +interface AdSpaceWrapperProps { + children: React.ReactElement; + withoutHeader?: boolean; +} + +const AdSpaceWrapper: React.FC = ({ children, withoutHeader }) => { + const { t } = useTranslation(); + return ( + + {withoutHeader ? null : {t("misc.ad")}} + {children} + + ); +}; + +const AdSpace: React.FC = ({ name, unitType, withoutHeader }) => { const theme = useTheme(); - const isDevMode = useDevMode(); - const events = useAtomValue(eventsAtom); + const [events, setEvents] = useAtom(eventsAtom); const selectorName = `dbu_${md5(name + unitType.toString())}`; + const size = unitType in adSpaceSize ? adSpaceSize[unitType as keyof typeof adSpaceSize] : null; + useEffect(() => { + if (events.playwireInitializedUnits.indexOf(name) > -1) { + log.error(`ramp: Unit ${name} (${unitType}) has already been initialized...`); + return; + } + + if (DB_DISPLAY_AD_PLACEHOLDERS) { + setEvents(playwireAddInitializedUnit(name)); + log.debug(`not ramp: initialized unit ${name} (${unitType})`); + } + + if (!adsEnabled) { + return; + } + if (!events.playwireSetupHasFinished) { return; } @@ -46,34 +90,46 @@ const AdSpace: React.FC = ({ name, unitType }) => { log.error("ramp: could not add unit", { error }); window.ramp.displayUnits(); } + + setEvents(playwireAddInitializedUnit(name)); + log.debug(`ramp: initialized unit ${name} (${unitType})`); }; initUnit(); - }, [events.playwireSetupHasFinished, selectorName, unitType]); + }, [events.playwireSetupHasFinished, events.playwireInitializedUnits, selectorName, unitType, setEvents, name]); - if (!adsEnabled) { - return null; - } - - if (isDevMode) { + if (DB_DISPLAY_AD_PLACEHOLDERS) { return ( - - - + + + + + ); } - return
; + if (!adsEnabled) { + return null; + } + + return ( + +
+ + ); }; export default React.memo(AdSpace); diff --git a/src/components/AdSpaceFloating.tsx b/src/components/AdSpaceFloating.tsx index ef2bc7a31..184eeb415 100644 --- a/src/components/AdSpaceFloating.tsx +++ b/src/components/AdSpaceFloating.tsx @@ -1,55 +1,66 @@ import { Box, useTheme } from "@mui/material"; +import { Breakpoint } from "@mui/system"; import AdSpace, { UnitType } from "@src/components/AdSpace"; -import useIsMobile from "@src/hooks/is-mobile"; import useWindowSize from "@src/hooks/window-size"; import { adsEnabled } from "@src/utils/env-tools"; import React from "react"; -export const adSpaceRightSideMinSize = 300; -export const adSpaceMobileBannerHeight = 96; +interface AdSpaceFloatingProps { + name: string; + unitType: UnitType; + fromBreakpoint?: Breakpoint; + untilBreakpoint?: Breakpoint; + left?: number; + right?: number; + top?: number; + bottom?: number; + withoutHeader?: boolean; +} -const AdSpaceFloating = () => { +const AdSpaceFloating: React.FC = ({ + name, + unitType, + fromBreakpoint, + untilBreakpoint, + left, + right, + top, + bottom, + withoutHeader, +}) => { const theme = useTheme(); - const isMobile = useIsMobile(); const { width } = useWindowSize(); - if (!adsEnabled) { + if (!adsEnabled && !DB_DISPLAY_AD_PLACEHOLDERS) { return null; } - const rightSideSpace = (width - theme.breakpoints.values.xl) * 0.5; - const rightSideHasEnoughSpace = rightSideSpace > adSpaceRightSideMinSize; + const canRenderUsingFromRule = fromBreakpoint ? width >= theme.breakpoints.values[fromBreakpoint] : true; + const canRenderUsingUntilRule = untilBreakpoint ? width <= theme.breakpoints.values[untilBreakpoint] : true; - const baseStyle = { - background: theme.palette.background.default, - display: "flex", - position: "fixed", - }; - - if (!isMobile && !rightSideHasEnoughSpace) { + if (!canRenderUsingFromRule || !canRenderUsingUntilRule) { return null; } - const style = { - ...baseStyle, - ...(isMobile - ? { - bottom: 0, - height: `${adSpaceMobileBannerHeight}px`, - left: 0, - right: 0, - } - : { - bottom: 0, - right: 0, - top: 64, - width: `${rightSideSpace - adSpaceRightSideMinSize * 0.5}px`, - }), - }; - return ( - - + + ); }; diff --git a/src/components/BuildMenu.tsx b/src/components/BuildMenu.tsx index 1330c94d2..1b93103d9 100644 --- a/src/components/BuildMenu.tsx +++ b/src/components/BuildMenu.tsx @@ -1,9 +1,11 @@ import { Bookmark, BookmarkBorder, ContentCopy, Undo } from "@mui/icons-material"; import { Fab, IconButton, useTheme } from "@mui/material"; -import { adSpaceMobileBannerHeight } from "@src/components/AdSpaceFloating"; +import { adSpaceSize, UnitType } from "@src/components/AdSpace"; import InputDialog from "@src/components/InputDialog"; +import { playwireUnitMobileBottomRail } from "@src/constants"; import useIsMobile from "@src/hooks/is-mobile"; import { buildModelView, lastSelectedBuildModelView } from "@src/state/build"; +import { eventsAtom, playwireIsUnitInitialized } from "@src/state/events"; import { addFavorite, favoritesAtom, @@ -41,6 +43,8 @@ const BuildMenu: React.FC = () => { const isFavorite = isBuildInFavorites(favorites, buildId); const isCopyToClipboardEnabled = navigator.clipboard !== undefined; + const events = useAtomValue(eventsAtom); + if (buildIdRegex.exec(location.pathname) === null) { return null; } @@ -95,7 +99,14 @@ const BuildMenu: React.FC = () => { color="primary" onClick={handleCopyToClipboardClicked} sx={{ - bottom: adsEnabled ? `${adSpaceMobileBannerHeight}px` : theme.spacing(2), + bottom: + (adsEnabled || DB_DISPLAY_AD_PLACEHOLDERS) && + playwireIsUnitInitialized(events, playwireUnitMobileBottomRail) + ? `${ + adSpaceSize[UnitType.BottomRail].height + + parseInt(theme.spacing(3).slice(0, -2)) + }px` + : theme.spacing(2), // TODO: edit this position: "fixed", right: theme.spacing(3), }} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 86036ae38..5a98e6130 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -31,14 +31,21 @@ import { Typography, useTheme, } from "@mui/material"; -import AdSpace, { UnitType } from "@src/components/AdSpace"; -import AdSpaceFloating, { adSpaceRightSideMinSize } from "@src/components/AdSpaceFloating"; +import { UnitType } from "@src/components/AdSpace"; +import AdSpaceFloating from "@src/components/AdSpaceFloating"; import BuildMenu from "@src/components/BuildMenu"; import LinkBox from "@src/components/LinkBox"; import SomethingWentWrong from "@src/components/SometingWentWrong"; import Spacer from "@src/components/Spacer"; import { drawerWidth } from "@src/components/theme"; -import { crowdinLink, discordServerUrl, githubUrl, xTwitterUrl } from "@src/constants"; +import { + crowdinLink, + discordServerUrl, + githubUrl, + playwireUnitMobileBottomRail, + playwireUnitRightSide, + xTwitterUrl, +} from "@src/constants"; import dauntlessBuilderData from "@src/data/Data"; import useDevMode from "@src/hooks/dev-mode"; import useIsMobile from "@src/hooks/is-mobile"; @@ -263,7 +270,23 @@ const Layout: React.FC = ({ children }) => { - + + ); }; diff --git a/src/components/TrackingRampSetup.tsx b/src/components/TrackingRampSetup.tsx index 521e8370c..2d5ea744b 100644 --- a/src/components/TrackingRampSetup.tsx +++ b/src/components/TrackingRampSetup.tsx @@ -1,5 +1,4 @@ -import { UnitType } from "@src/components/AdSpace"; -import { eventsAtom, playwireSetupHasFinished } from "@src/state/events"; +import { eventsAtom, playwireClearInitializedUnits, playwireSetupHasFinished } from "@src/state/events"; import { adsEnabled } from "@src/utils/env-tools"; import log from "@src/utils/logger"; import { useSetAtom } from "jotai"; @@ -19,7 +18,7 @@ const TrackingRampSetup = () => { log.debug("enabled GA4"); } - if (adsEnabled) { + if (adsEnabled && !DB_DISPLAY_AD_PLACEHOLDERS) { if (window.ramp) { return; } @@ -43,7 +42,7 @@ const TrackingRampSetup = () => { } window.ramp.que.push(() => { - log.debug("playwire has been setup") + log.debug("playwire has been setup"); setEvents(playwireSetupHasFinished()); }); @@ -52,6 +51,25 @@ const TrackingRampSetup = () => { rampScript.async = true; document.body.appendChild(rampScript); } + + if (adsEnabled || DB_DISPLAY_AD_PLACEHOLDERS) { + let timer: NodeJS.Timeout | null = null; + + window.addEventListener("resize", () => { + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(() => { + if (!window.ramp) { + return; + } + + log.debug("resized window: destroy units..."); + window.ramp.destroyUnits("all"); + setEvents(playwireClearInitializedUnits()); + }, 100); + }); + } }, [setEvents]); return null; diff --git a/src/constants.ts b/src/constants.ts index 922569922..7551fe252 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,3 +5,6 @@ export const discordServerUrl = "https://discord.gg/hkMvhsfPjH"; export const crowdinLink = "https://crowdin.com/project/dauntless-builder"; export const licenseUrl = "https://github.com/atomicptr/dauntless-builder/blob/master/LICENSE"; export const assetsCdnPrefix = "https://cdn.jsdelivr.net/gh/atomicptr/dauntless-builder"; + +export const playwireUnitRightSide = "SkyscraperRightSide"; +export const playwireUnitMobileBottomRail = "MobileBottomRail"; diff --git a/src/json/i18n/en/en.json b/src/json/i18n/en/en.json index 6e229389b..5dde774a9 100644 --- a/src/json/i18n/en/en.json +++ b/src/json/i18n/en/en.json @@ -42,6 +42,7 @@ }, "feature-disabled-browser": "This feature is currently disabled for this web browser.", "misc": { + "ad": "Ad", "dauntless-version": "Dauntless v{{version}}", "discord-server": "Dauntless Builder Discord Server", "github-repository": "Github Repository", diff --git a/src/pages/about/privacy.html b/src/pages/about/privacy.html index 74494e10a..21788a31a 100644 --- a/src/pages/about/privacy.html +++ b/src/pages/about/privacy.html @@ -80,26 +80,30 @@

Our Advertising Partners

hyperlinked to their Privacy Policies below.

- +

Google

+ +https://policies.google.com/technologies/ads

Playwire LLC

-

All or partial advertising on this Website or App is managed by Playwire LLC. If Playwire publisher advertising +

+ All or partial advertising on this Website or App is managed by Playwire LLC. If Playwire publisher advertising services are used, Playwire LLC may collect and use certain aggregated and anonymized data for advertising purposes. - To learn more about the types of data collected, how data is used and your choices as a user, please visit https://www.playwire.com/privacy-policy.

-

For EU Users only: If you are located in countries that are part of the European Economic Area, in + To learn more about the types of data collected, how data is used and your choices as a user, please visit + https://www.playwire.com/privacy-policy. +

+

+ For EU Users only: If you are located in countries that are part of the European Economic Area, in the United Kingdom or Switzerland, and publisher advertising services are being provided by Playwire LLC, you were presented with messaging from our Consent Management Platform (CMP) around your privacy choices as a user in regards to digital advertising, applicable vendors, cookie usage and more. If you’d like to revisit the choices you have - made previously on this Website or App, please click here.

- + made previously on this Website or App, please + click here. +

Advertising Partners Privacy Policies

diff --git a/src/state/events.ts b/src/state/events.ts index aac80b746..815e17a84 100644 --- a/src/state/events.ts +++ b/src/state/events.ts @@ -2,10 +2,19 @@ import { atom } from "jotai"; export interface EventsState { playwireSetupHasFinished: boolean; + playwireInitializedUnits: string[]; } export const eventsAtom = atom({ + playwireInitializedUnits: [], playwireSetupHasFinished: false, }); export const playwireSetupHasFinished = () => (state: EventsState) => ({ ...state, playwireSetupHasFinished: true }); +export const playwireClearInitializedUnits = () => (state: EventsState) => ({ ...state, playwireInitializedUnits: [] }); +export const playwireAddInitializedUnit = (unitName: string) => (state: EventsState) => ({ + ...state, + playwireInitializedUnits: [...state.playwireInitializedUnits, unitName], +}); +export const playwireIsUnitInitialized = (state: EventsState, unitName: string): boolean => + state.playwireInitializedUnits.indexOf(unitName) > -1; diff --git a/vite.config.ts b/vite.config.ts index f34fd335a..35252f1a6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -46,6 +46,7 @@ export default defineConfig(({ command, mode }) => { define: { DB_BUILD_TIME: Date.now(), DB_DEVMODE: isDevMode, + DB_DISPLAY_AD_PLACEHOLDERS: env("DB_DISPLAY_AD_PLACEHOLDERS", "bool"), DB_ENABLE_ADS: env("DB_ENABLE_ADS", "bool"), DB_GA4_MEASUREMENT_ID:env("DB_GA4_MEASUREMENT_ID"), DB_PW_PUBLISHER_ID: env("DB_PW_PUBLISHER_ID"), From 3836706b377f3c7eeac07c4ddddb45b382c30ab5 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Tue, 14 Nov 2023 21:37:09 +0900 Subject: [PATCH 17/21] play around with pw units --- src/components/AdSpace.tsx | 75 ++++++++++-------------------- src/components/AdSpaceFloating.tsx | 3 -- src/components/CenterBox.tsx | 22 +++++++++ src/components/ConstraintBox.tsx | 54 +++++++++++++++++++++ src/components/Layout.tsx | 45 ++++++++++++------ src/constants.ts | 3 ++ src/pages/build/Build.tsx | 13 ++++++ 7 files changed, 148 insertions(+), 67 deletions(-) create mode 100644 src/components/CenterBox.tsx create mode 100644 src/components/ConstraintBox.tsx diff --git a/src/components/AdSpace.tsx b/src/components/AdSpace.tsx index 37b3cbbd6..7f4eec7f9 100644 --- a/src/components/AdSpace.tsx +++ b/src/components/AdSpace.tsx @@ -1,55 +1,34 @@ import { FeaturedVideo } from "@mui/icons-material"; -import { Box, ListSubheader, useTheme } from "@mui/material"; +import { Box, useTheme } from "@mui/material"; import { eventsAtom, playwireAddInitializedUnit } from "@src/state/events"; import { adsEnabled } from "@src/utils/env-tools"; import log from "@src/utils/logger"; import { useAtom } from "jotai"; import md5 from "md5"; import React, { useEffect } from "react"; -import { useTranslation } from "react-i18next"; export enum UnitType { BottomRail = "bottom_rail", Skyscraper160x600 = "sky_atf", Skyscraper300x600 = "sky_btf", + MediumRect300x250 = "med_rect_atf", + MediumRect320x50 = "med_rect_ctf", } export const adSpaceSize = { [UnitType.BottomRail]: { height: 50, width: 320 }, [UnitType.Skyscraper160x600]: { height: 600, width: 160 }, [UnitType.Skyscraper300x600]: { height: 600, width: 300 }, + [UnitType.MediumRect300x250]: { height: 250, width: 300 }, + [UnitType.MediumRect320x50]: { height: 50, width: 320 }, }; interface AdSpaceProps { name: string; unitType: UnitType; - withoutHeader?: boolean; } -interface AdSpaceWrapperProps { - children: React.ReactElement; - withoutHeader?: boolean; -} - -const AdSpaceWrapper: React.FC = ({ children, withoutHeader }) => { - const { t } = useTranslation(); - return ( - - {withoutHeader ? null : {t("misc.ad")}} - {children} - - ); -}; - -const AdSpace: React.FC = ({ name, unitType, withoutHeader }) => { +const AdSpace: React.FC = ({ name, unitType }) => { const theme = useTheme(); const [events, setEvents] = useAtom(eventsAtom); @@ -99,25 +78,23 @@ const AdSpace: React.FC = ({ name, unitType, withoutHeader }) => { if (DB_DISPLAY_AD_PLACEHOLDERS) { return ( - - - - - + + + ); } @@ -125,11 +102,7 @@ const AdSpace: React.FC = ({ name, unitType, withoutHeader }) => { return null; } - return ( - -
- - ); + return
; }; export default React.memo(AdSpace); diff --git a/src/components/AdSpaceFloating.tsx b/src/components/AdSpaceFloating.tsx index 184eeb415..a39cbc3c1 100644 --- a/src/components/AdSpaceFloating.tsx +++ b/src/components/AdSpaceFloating.tsx @@ -14,7 +14,6 @@ interface AdSpaceFloatingProps { right?: number; top?: number; bottom?: number; - withoutHeader?: boolean; } const AdSpaceFloating: React.FC = ({ @@ -26,7 +25,6 @@ const AdSpaceFloating: React.FC = ({ right, top, bottom, - withoutHeader, }) => { const theme = useTheme(); const { width } = useWindowSize(); @@ -59,7 +57,6 @@ const AdSpaceFloating: React.FC = ({ ); diff --git a/src/components/CenterBox.tsx b/src/components/CenterBox.tsx new file mode 100644 index 000000000..c81c001a5 --- /dev/null +++ b/src/components/CenterBox.tsx @@ -0,0 +1,22 @@ +import { Box } from "@mui/material"; +import React from "react"; + +interface CenterBoxProps { + flexGrow?: number; + children: React.ReactElement; +} + +const CenterBox: React.FC = ({ flexGrow, children }) => ( + + {children} + +); + +export default React.memo(CenterBox); diff --git a/src/components/ConstraintBox.tsx b/src/components/ConstraintBox.tsx new file mode 100644 index 000000000..404f249cc --- /dev/null +++ b/src/components/ConstraintBox.tsx @@ -0,0 +1,54 @@ +import useWindowSize from "@src/hooks/window-size"; +import React from "react"; + +export interface Constraint { + matchesConstraint(windowWidth: number, windowHeight: number): boolean; +} + +export class WithinBoundsConstraint implements Constraint { + maxWidth: number; + maxHeight: number; + + constructor(maxWidth: number, maxHeight: number) { + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + } + + matchesConstraint(windowWidth: number, windowHeight: number): boolean { + return windowWidth <= this.maxWidth && windowHeight <= this.maxHeight; + } +} + +export class OutsideBoundsConstraint implements Constraint { + minWidth: number; + minHeight: number; + + constructor(minWidth: number, minHeight: number) { + this.minWidth = minWidth; + this.minHeight = minHeight; + } + + matchesConstraint(windowWidth: number, windowHeight: number): boolean { + return windowWidth >= this.minWidth && windowHeight >= this.minHeight; + } +} + +interface ConstraintBoxProps { + constraints: Constraint[]; + children: React.ReactElement; + renderOnFail?: React.ReactElement | null; +} + +const ConstraintBox: React.FC = ({ constraints, children, renderOnFail }) => { + const { width, height } = useWindowSize(); + + for (const constraint of constraints) { + if (!constraint.matchesConstraint(width, height)) { + return renderOnFail; + } + } + + return children; +}; + +export default React.memo(ConstraintBox); diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 5a98e6130..929aba87e 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -31,9 +31,11 @@ import { Typography, useTheme, } from "@mui/material"; -import { UnitType } from "@src/components/AdSpace"; +import AdSpace, { UnitType } from "@src/components/AdSpace"; import AdSpaceFloating from "@src/components/AdSpaceFloating"; import BuildMenu from "@src/components/BuildMenu"; +import CenterBox from "@src/components/CenterBox"; +import ConstraintBox, { OutsideBoundsConstraint, WithinBoundsConstraint } from "@src/components/ConstraintBox"; import LinkBox from "@src/components/LinkBox"; import SomethingWentWrong from "@src/components/SometingWentWrong"; import Spacer from "@src/components/Spacer"; @@ -42,8 +44,9 @@ import { crowdinLink, discordServerUrl, githubUrl, + playwireUnitLeftSide, + playwireUnitLeftSideSmall, playwireUnitMobileBottomRail, - playwireUnitRightSide, xTwitterUrl, } from "@src/constants"; import dauntlessBuilderData from "@src/data/Data"; @@ -51,6 +54,7 @@ import useDevMode from "@src/hooks/dev-mode"; import useIsMobile from "@src/hooks/is-mobile"; import { currentLanguage, getNativeLanguageName, isBetaLanguage, Language } from "@src/i18n"; import { favoritesView } from "@src/state/favorites"; +import { adsEnabled } from "@src/utils/env-tools"; import log from "@src/utils/logger"; import { useAtomValue } from "jotai"; import React, { ReactNode, useState } from "react"; @@ -144,7 +148,7 @@ const Layout: React.FC = ({ children }) => { /> ) : null} - {/* Spacer */} + @@ -191,7 +195,31 @@ const Layout: React.FC = ({ children }) => { ))} - + {adsEnabled || DB_DISPLAY_AD_PLACEHOLDERS ? ( + + <> + + + + + + + + + ) : ( + + )} = ({ children }) => { - = ({ children }) => { right={0} unitType={UnitType.BottomRail} untilBreakpoint={"md"} - withoutHeader /> ); diff --git a/src/constants.ts b/src/constants.ts index 7551fe252..9c751de87 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,3 +8,6 @@ export const assetsCdnPrefix = "https://cdn.jsdelivr.net/gh/atomicptr/dauntless- export const playwireUnitRightSide = "SkyscraperRightSide"; export const playwireUnitMobileBottomRail = "MobileBottomRail"; +export const playwireUnitLeftSide = "LeftSideBar"; +export const playwireUnitUnderPerkList = "UnderPerkList"; +export const playwireUnitLeftSideSmall = "LeftSideBarSmaller"; diff --git a/src/pages/build/Build.tsx b/src/pages/build/Build.tsx index 01e5bf6ba..d5625cbc9 100644 --- a/src/pages/build/Build.tsx +++ b/src/pages/build/Build.tsx @@ -1,10 +1,13 @@ import { ManageSearch } from "@mui/icons-material"; import { Box, Button, Grid, ListSubheader, Typography } from "@mui/material"; +import AdSpace, { UnitType } from "@src/components/AdSpace"; import BondWeaponPicker from "@src/components/BondWeaponPicker"; import BuildWarning from "@src/components/BuildWarning"; import CellPicker from "@src/components/CellPicker"; import CellSelectDialog from "@src/components/CellSelectDialog"; import CellSlotFilter from "@src/components/CellSlotFilter"; +import CenterBox from "@src/components/CenterBox"; +import ConstraintBox from "@src/components/ConstraintBox"; import ElementalTypeFilter from "@src/components/ElementalTypeFilter"; import GenericItemSelectDialog, { GenericItem } from "@src/components/GenericItemSelectDialog"; import ItemPicker, { ItemPickerItem } from "@src/components/ItemPicker"; @@ -25,6 +28,7 @@ import PerkListMobile from "@src/components/PerkListMobile"; import TagIcons from "@src/components/TagIcons"; import UniqueEffectCard from "@src/components/UniqueEffectCard"; import WeaponTypeFilter from "@src/components/WeaponTypeFilter"; +import { playwireUnitUnderPerkList } from "@src/constants"; import { Armour, ArmourType } from "@src/data/Armour"; import { BuildModel, findPartSlotName } from "@src/data/BuildModel"; import { CellType } from "@src/data/Cell"; @@ -493,6 +497,15 @@ const Build: React.FC = () => { sm={12} sx={{ width: isMobile ? "100%" : undefined }} > + + + + + + {isMobile ? : }