Skip to content

Commit 778c33d

Browse files
committed
Error screen preview
1 parent d80a29d commit 778c33d

File tree

9 files changed

+187
-15
lines changed

9 files changed

+187
-15
lines changed

special-pages/pages/duckplayer/app/components/DesktopApp.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import { useYouTubeError } from '../providers/YouTubeErrorProvider';
1616
export function DesktopApp({ embed }) {
1717
const settings = useSettings();
1818
const features = createAppFeaturesFrom(settings);
19+
const ytError = useYouTubeError();
20+
1921
return (
2022
<>
2123
<Background />
22-
{features.focusMode()}
24+
{!ytError && features.focusMode()}
2325
<main class={styles.app}>
2426
<DesktopLayout embed={embed} />
2527
</main>

special-pages/pages/duckplayer/app/components/MobileApp.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ const DISABLED_HEIGHT = 450;
2323
export function MobileApp({ embed }) {
2424
const settings = useSettings();
2525
const telemetry = useTelemetry();
26+
const ytError = useYouTubeError();
27+
2628
const features = createAppFeaturesFrom(settings);
2729
return (
2830
<>
2931
<Background />
30-
{features.focusMode()}
32+
{!ytError && features.focusMode()}
3133
<OrientationProvider
3234
onChange={(orientation) => {
3335
if (orientation === 'portrait') {

special-pages/pages/duckplayer/app/components/Player.jsx

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import { useTypedTranslation } from '../types.js';
1010
export const PLAYER_ERRORS = {
1111
invalidId: 'invalid-id',
1212
botDetected: 'bot-detected',
13+
ageRestricted: 'age-restricted',
14+
noEmbed: 'no-embed',
1315
}
1416

17+
export const PLAYER_ERROR_IDS = Object.values(PLAYER_ERRORS);
18+
1519
/**
16-
* @typedef {typeof PLAYER_ERRORS[keyof typeof PLAYER_ERRORS]} PlayerError
20+
* @typedef {'invalid-id'|'bot-detected'|'age-restricted'|'no-embed'} PlayerError
1721
*/
1822

1923
/**
@@ -53,27 +57,89 @@ export function Player({ src, layout }) {
5357
}
5458

5559
/**
56-
* @param {object} props
57-
* @param {Settings['layout']} props.layout
58-
* @param {PlayerError} props.kind
60+
* @param {PlayerError} kind
61+
* @returns {{heading: Element, message: Element, solutions: Element[]}}
5962
*/
60-
export function PlayerError({ kind, layout }) {
63+
function useErrorStrings(kind) {
6164
const { t } = useTypedTranslation();
62-
const errors = {
65+
const headingsMap = {
6366
['invalid-id']: <span dangerouslySetInnerHTML={{ __html: t('invalidIdError') }} />,
6467
['bot-detected']: <span dangerouslySetInnerHTML={{ __html: t('botDetectedError') }} />,
68+
['age-restricted']: <span dangerouslySetInnerHTML={{ __html: t('blockedVideoError') }} />,
6569
};
66-
const text = errors[kind] || errors['invalid-id'];
70+
const solutionsMap = {
71+
['invalid-id']: [],
72+
['bot-detected']: [
73+
<span dangerouslySetInnerHTML={{ __html: t('botDetectedErrorTip1') }} />,
74+
<span dangerouslySetInnerHTML={{ __html: t('botDetectedErrorTip2') }} />,
75+
]
76+
}
77+
const messageMap = {
78+
['invalid-id']: '',
79+
['age-restricted']: <span dangerouslySetInnerHTML={{ __html: t('blockedVideoErrorMessage') }} />,
80+
}
81+
82+
const heading = headingsMap[kind] || headingsMap['invalid-id'];
83+
const solutions = solutionsMap[kind] || solutionsMap['invalid-id'];
84+
const message = messageMap[kind] || messageMap['invalid-id'];
85+
return { heading, message, solutions };
86+
}
6787

88+
/**
89+
* @param {object} props
90+
* @param {Settings['layout']} props.layout
91+
* @param {PlayerError} props.kind
92+
*/
93+
export function PlayerError({ kind, layout }) {
6894
return (
6995
<div
7096
class={cn(styles.root, {
7197
[styles.desktop]: layout === 'desktop',
7298
[styles.mobile]: layout === 'mobile',
7399
})}
74100
>
75-
<div className={styles.error}>
76-
<p>{text}</p>
101+
{ kind === 'invalid-id' && <InvalidIdError kind={kind} />}
102+
{ kind !== 'invalid-id' && <YouTubeError kind={kind} /> }
103+
</div>
104+
);
105+
}
106+
107+
/**
108+
* @param {object} props
109+
* @param {PlayerError} props.kind
110+
*/
111+
export function InvalidIdError({ kind }) {
112+
const { heading } = useErrorStrings(kind);
113+
const classes = cn(styles.error, styles.invalidError);
114+
115+
return (
116+
<div className={classes}>
117+
<p>{heading}</p>
118+
</div>
119+
);
120+
}
121+
122+
/**
123+
* @param {object} props
124+
* @param {PlayerError} props.kind
125+
*/
126+
export function YouTubeError({ kind }) {
127+
const { heading, message, solutions } = useErrorStrings(kind);
128+
const classes = cn(styles.error, styles.youtubeError);
129+
130+
return (
131+
<div className={classes}>
132+
<div className={styles.youtubeErrorContainer}>
133+
<span className={styles.youtubeErrorIcon}></span>
134+
135+
<h1 className={styles.youtubeErrorHeading}>{heading}</h1>
136+
137+
{message && <p className={styles.youtubeErrorMessage}>{message}</p>}
138+
139+
{solutions &&
140+
<ul className={styles.youtubeErrorList}>
141+
{solutions.map(item => <li>{item}</li>)}
142+
</ul>}
77143
</div>
78144
</div>
79145
);

special-pages/pages/duckplayer/app/components/Player.module.css

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,69 @@
4747
background: #2f2f2f;
4848
}
4949

50+
.youtubeError {
51+
background: rgba(0, 0, 0, 0.6);
52+
}
53+
54+
.youtubeErrorContainer {
55+
display: grid;
56+
column-gap: 24px;
57+
row-gap: 8px;
58+
grid-template-areas:
59+
"icon heading"
60+
"icon message"
61+
"icon list";
62+
max-width: 680px;
63+
padding: 0 40px;
64+
}
65+
66+
.youtubeErrorIcon {
67+
align-self: start;
68+
background: url('../img/warning-96.data.svg') no-repeat;
69+
display: block;
70+
height: 48px;
71+
width: 48px;
72+
73+
grid-area: icon;
74+
75+
@media screen and (min-width: 600px) {
76+
align-self: start;
77+
background-image: url('../img/warning-128.data.svg');
78+
height: 96px;
79+
width: 128px;
80+
}
81+
}
82+
83+
.youtubeErrorHeading {
84+
font-size: 20px;
85+
font-weight: 700;
86+
line-height: calc(24 / 20);
87+
margin: 0;
88+
89+
grid-area: heading;
90+
}
91+
92+
.youtubeErrorMessage {
93+
color: #ccc;
94+
font-size: 14px;
95+
line-height: calc(20 / 14);
96+
97+
grid-area: message;
98+
}
99+
100+
.youtubeErrorList {
101+
color: #ccc;
102+
font-size: 14px;
103+
line-height: calc(20 / 14);
104+
105+
li {
106+
list-style: disc;
107+
margin-left: 20px;
108+
}
109+
110+
grid-area: list
111+
}
112+
50113
@media screen and (min-width: 600px) and (max-height: 450px) {
51114
.root.mobile {
52115
max-height: 100%;
Lines changed: 8 additions & 0 deletions
Loading
Lines changed: 7 additions & 0 deletions
Loading

special-pages/pages/duckplayer/app/index.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Fallback } from '../../../shared/components/Fallback/Fallback.jsx';
1515
import { Components } from './components/Components.jsx';
1616
import { MobileApp } from './components/MobileApp.jsx';
1717
import { DesktopApp } from './components/DesktopApp.jsx';
18+
import { PLAYER_ERROR_IDS } from './components/Player';
1819

1920
/**
2021
* @param {import("../src/index.js").DuckplayerPage} messaging
@@ -60,6 +61,13 @@ export async function init(messaging, telemetry, baseEnvironment) {
6061

6162
console.log(settings);
6263

64+
// TODO: Refactor
65+
let initialYouTubeError = null;
66+
const ytErrorParam = baseEnvironment.urlParams.get('youtubeError');
67+
if (ytErrorParam && PLAYER_ERROR_IDS.includes(ytErrorParam)) {
68+
initialYouTubeError = /** @type {import('./components/Player').PlayerError} */(ytErrorParam);
69+
}
70+
6371
const embed = createEmbedSettings(window.location.href, settings);
6472

6573
const didCatch = (error) => {
@@ -80,7 +88,7 @@ export async function init(messaging, telemetry, baseEnvironment) {
8088
<TelemetryContext.Provider value={telemetry}>
8189
<MessagingContext.Provider value={messaging}>
8290
<SettingsProvider settings={settings}>
83-
<YouTubeErrorProvider>
91+
<YouTubeErrorProvider initial={initialYouTubeError}>
8492
<UserValuesProvider initial={init.userValues}>
8593
{settings.layout === 'desktop' && (
8694
<TranslationProvider

special-pages/pages/duckplayer/app/providers/YouTubeErrorProvider.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useContext, useState } from 'preact/hooks';
22
import { h, createContext } from 'preact';
33
import { useEffect } from 'preact/hooks';
4-
import { PLAYER_ERRORS } from '../components/Player';
4+
import { PLAYER_ERROR_IDS } from '../components/Player';
55

66
export const IFRAME_ERROR_EVENT = 'iframe-error';
77

@@ -28,7 +28,7 @@ export function YouTubeErrorProvider({ initial = null, children }) {
2828
/** @type {(event: CustomEvent) => void} */
2929
const errorEventHandler = (event) => {
3030
const error = event.detail?.error
31-
if (Object.values(PLAYER_ERRORS).includes(error) || error === null) {
31+
if (PLAYER_ERROR_IDS.includes(error) || error === null) {
3232
setError(error);
3333
}
3434
};

special-pages/pages/duckplayer/public/locales/en/duckplayer.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,25 @@
3434
"note": "Shown when the page URL doesn't match a known video ID. Note for translators: The <b> tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized."
3535
},
3636
"botDetectedError": {
37-
"title": "<b>ERROR:</b> Bot detected",
37+
"title": "We are not able to connect you with this video",
3838
"note": "Message for bot detected"
3939
},
40+
"botDetectedErrorTip1": {
41+
"title": "If you use a VPN, try turning it off and reload the video.",
42+
"note": "A tip on how to solve the bot detected error"
43+
},
44+
"botDetectedErrorTip2": {
45+
"title": "If turning off the VPN doesn’t work, you can view this video on YouTube without the added privacy features of Duck Player.",
46+
"note": "A tip on how to solve the bot detected error"
47+
},
48+
"blockedVideoError": {
49+
"title": "YouTube has blocked this video in Duck Player",
50+
"note": "Message for when the video is blocked by YouTube"
51+
},
52+
"blockedVideoErrorMessage": {
53+
"title": "View this video on YouTube without the added privacy features of Duck Player.",
54+
"note": "A suggested solution to the blocked video error is to watch it directly on YouTube. This message suggests that as a solution to the user."
55+
},
4056
"tooltipInfo": {
4157
"title": "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations."
4258
}

0 commit comments

Comments
 (0)