[web] Cast improvements (#1659)
This commit is contained in:
commit
d69ff79a67
6 changed files with 137 additions and 130 deletions
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
||||
`;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Reference in a new issue