[web] Cast improvements (#1659)

This commit is contained in:
Manav Rathi 2024-05-08 20:26:06 +05:30 committed by GitHub
commit d69ff79a67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 137 additions and 130 deletions

View file

@ -1,46 +0,0 @@
import { FilledCircleCheck } from "./FilledCircleCheck";
export const PairedSuccessfullyOverlay: React.FC = () => {
return (
<div
style={{
position: "fixed",
top: 0,
right: 0,
height: "100%",
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: 100,
backgroundColor: "black",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
flexDirection: "column",
textAlign: "center",
}}
>
<FilledCircleCheck />
<h2
style={{
marginBottom: 0,
}}
>
Pairing Complete
</h2>
<p
style={{
lineHeight: "1.5rem",
}}
>
We're preparing your album.
<br /> This should only take a few seconds.
</p>
</div>
</div>
);
};

View file

@ -1,6 +1,6 @@
import { styled } from "@mui/material";
const colourPool = [
const colors = [
"#87CEFA", // Light Blue
"#90EE90", // Light Green
"#F08080", // Light Coral
@ -23,27 +23,34 @@ const colourPool = [
"#808000", // Light Olive
];
export const LargeType = ({ chars }: { chars: string[] }) => {
interface PairingCodeProps {
code: string;
}
export const PairingCode: React.FC<PairingCodeProps> = ({ code }) => {
return (
<Container style={{}}>
{chars.map((char, i) => (
<PairingCode_>
{code.split("").map((char, i) => (
<span
key={i}
style={{
// alternating background
// Alternating background.
backgroundColor: i % 2 === 0 ? "#2e2e2e" : "#5e5e5e",
// varying colors
color: colourPool[i % colourPool.length],
// Varying colors.
color: colors[i % colors.length],
}}
>
{char}
</span>
))}
</Container>
</PairingCode_>
);
};
const Container = styled("div")`
const PairingCode_ = styled("div")`
border-radius: 10px;
overflow: hidden;
font-size: 4rem;
font-weight: bold;
font-family: monospace;

View file

@ -1,6 +1,7 @@
import log from "@/next/log";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import { LargeType } from "components/LargeType";
import { styled } from "@mui/material";
import { PairingCode } from "components/PairingCode";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { storeCastData } from "services/cast";
@ -65,65 +66,52 @@ export default function Index() {
};
return (
<>
<div
style={{
height: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<div
style={{
textAlign: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<img width={150} src="/images/ente.svg" />
<h1
style={{
fontWeight: "normal",
}}
>
Enter this code on <b>Ente Photos</b> to pair this
screen
</h1>
<div
style={{
borderRadius: "10px",
overflow: "hidden",
}}
>
{pairingCode ? (
<LargeType chars={pairingCode.split("")} />
) : (
<EnteSpinner />
)}
</div>
<p
style={{
fontSize: "1.2rem",
}}
>
Visit{" "}
<a
style={{
textDecoration: "none",
color: "#87CEFA",
fontWeight: "bold",
}}
href="https://ente.io/cast"
target="_blank"
>
ente.io/cast
</a>{" "}
for help
</p>
</div>
</div>
</>
<Container>
<img width={150} src="/images/ente.svg" />
<h1>
Enter this code on <b>Ente Photos</b> to pair this screen
</h1>
{pairingCode ? <PairingCode code={pairingCode} /> : <Spinner />}
<p>
Visit{" "}
<a href="https://ente.io/cast" target="_blank">
ente.io/cast
</a>{" "}
for help
</p>
</Container>
);
}
const Container = styled("div")`
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
h1 {
font-weight: normal;
}
p {
font-size: 1.2rem;
}
a {
text-decoration: none;
color: #87cefa;
font-weight: bold;
}
`;
const Spinner: React.FC = () => (
<Spinner_>
<EnteSpinner />
</Spinner_>
);
const Spinner_ = styled("div")`
/* Roughly same height as the pairing code section to roduce layout shift */
margin-block: 1.7rem;
`;

View file

@ -1,5 +1,6 @@
import log from "@/next/log";
import { PairedSuccessfullyOverlay } from "components/PairedSuccessfullyOverlay";
import { styled } from "@mui/material";
import { FilledCircleCheck } from "components/FilledCircleCheck";
import { SlideView } from "components/Slide";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
@ -9,6 +10,7 @@ export default function Slideshow() {
const [loading, setLoading] = useState(true);
const [imageURL, setImageURL] = useState<string | undefined>();
const [nextImageURL, setNextImageURL] = useState<string | undefined>();
const [isEmpty, setIsEmpty] = useState(false);
const router = useRouter();
@ -24,8 +26,10 @@ export default function Slideshow() {
while (!stop) {
const { value: urls, done } = await urlGenerator.next();
if (done) {
log.warn("Empty collection");
pair();
// No items in this callection can be shown.
setIsEmpty(true);
// Go back to pairing screen after 3 seconds.
setTimeout(pair, 5000);
return;
}
@ -48,7 +52,61 @@ export default function Slideshow() {
console.log("Rendering slideshow", { loading, imageURL, nextImageURL });
if (loading) return <PairedSuccessfullyOverlay />;
if (loading) return <PairingComplete />;
if (isEmpty) return <NoItems />;
return <SlideView url={imageURL} nextURL={nextImageURL} />;
}
const PairingComplete: React.FC = () => {
return (
<Message>
<FilledCircleCheck />
<h2>Pairing Complete</h2>
<p>
We're preparing your album.
<br /> This should only take a few seconds.
</p>
</Message>
);
};
const Message: React.FC<React.PropsWithChildren> = ({ children }) => {
return (
<Message_>
<MessageItems>{children}</MessageItems>
</Message_>
);
};
const Message_ = styled("div")`
display: flex;
min-height: 100svh;
justify-content: center;
align-items: center;
line-height: 1.5rem;
h2 {
margin-block-end: 0;
}
`;
const MessageItems = styled("div")`
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
`;
const NoItems: React.FC = () => {
return (
<Message>
<h2>Try another album</h2>
<p>
This album has no photos that can be shown here
<br /> Please try another album
</p>
</Message>
);
};

View file

@ -103,10 +103,10 @@ export const renderableImageURLs = async function* (castData: CastData) {
*/
let lastYieldTime = Date.now();
// The first time around advance the lastYieldTime into the future so that
// The first time around regress the lastYieldTime into the past so that
// we don't wait around too long for the first slide (we do want to wait a
// bit, for the user to see the checkmark animation as reassurance).
lastYieldTime += 7500; /* 7.5 s */
lastYieldTime -= slideDuration - 2500; /* wait at most 2.5 s */
while (true) {
const encryptedFiles = shuffled(
@ -120,6 +120,7 @@ export const renderableImageURLs = async function* (castData: CastData) {
if (!isFileEligibleForCast(file)) continue;
console.log("will start createRenderableURL", new Date());
try {
urls.push(await createRenderableURL(castToken, file));
haveEligibleFiles = true;
@ -128,6 +129,8 @@ export const renderableImageURLs = async function* (castData: CastData) {
continue;
}
console.log("did end createRenderableURL", new Date());
// Need at least a pair.
//
// There are two scenarios:
@ -137,7 +140,7 @@ export const renderableImageURLs = async function* (castData: CastData) {
// - Subsequently, urls will have the "next" / "preloaded" URL left
// over from the last time. We'll promote that to being the one
// that'll get displayed, and preload another one.
if (urls.length < 2) continue;
// if (urls.length < 2) continue;
// The last element of previousURLs is the URL that is currently
// being shown on screen.
@ -150,15 +153,17 @@ export const renderableImageURLs = async function* (castData: CastData) {
// The URL that'll now get displayed on screen.
const url = ensure(urls.shift());
// The URL that we're preloading for next time around.
const nextURL = ensure(urls[0]);
const nextURL = ""; //ensure(urls[0]);
previousURLs.push(url);
const urlPair: RenderableImageURLPair = [url, nextURL];
const elapsedTime = Date.now() - lastYieldTime;
if (elapsedTime > 0 && elapsedTime < slideDuration)
if (elapsedTime > 0 && elapsedTime < slideDuration) {
console.log("waiting", slideDuration - elapsedTime);
await wait(slideDuration - elapsedTime);
}
lastYieldTime = Date.now();
yield urlPair;

View file

@ -118,11 +118,6 @@ export const advertiseCode = (
const options = new cast.framework.CastReceiverOptions();
// We don't use the media features of the Cast SDK.
options.skipPlayersLoad = true;
// TODO:Is this required? The docs say "(The default type of a message bus
// is JSON; if not provided here)."
options.customNamespaces = Object.assign({});
options.customNamespaces[namespace] =
cast.framework.system.MessageType.JSON;
// Do not stop the casting if the receiver is unreachable. A user should be
// able to start a cast on their phone and then put it away, leaving the
// cast running on their big screen.