Skip to content

Commit

Permalink
[web] Album cast dialog - Non functional tweaks (#3384)
Browse files Browse the repository at this point in the history
  • Loading branch information
mnvr authored Sep 21, 2024
2 parents 94a8ff2 + aae00dc commit dae0492
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 210 deletions.
2 changes: 1 addition & 1 deletion web/apps/cast/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { readCastData, storeCastData } from "services/cast-data";
import { getCastData, register } from "services/pair";
import { advertiseOnChromecast } from "../services/chromecast";
import { advertiseOnChromecast } from "../services/chromecast-receiver";

export default function Index() {
const [publicKeyB64, setPublicKeyB64] = useState<string | undefined>();
Expand Down
2 changes: 1 addition & 1 deletion web/apps/cast/src/pages/slideshow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FilledCircleCheck } from "components/FilledCircleCheck";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { readCastData } from "services/cast-data";
import { isChromecast } from "services/chromecast";
import { isChromecast } from "services/chromecast-receiver";
import { imageURLGenerator } from "services/render";

export default function Slideshow() {
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion web/apps/cast/src/services/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import HTTPService from "@ente/shared/network/HTTPService";
import type { AxiosResponse } from "axios";
import type { CastData } from "services/cast-data";
import { detectMediaMIMEType } from "services/detect-type";
import { isChromecast } from "./chromecast";
import { isChromecast } from "./chromecast-receiver";

/**
* An async generator function that loops through all the files in the
Expand Down
197 changes: 77 additions & 120 deletions web/apps/photos/src/components/Collections/AlbumCastDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
import { boxSeal } from "@/base/crypto/libsodium";
import log from "@/base/log";
import type { Collection } from "@/media/collection";
import { VerticallyCentered } from "@ente/shared/components/Container";
import { loadCast } from "@/new/photos/utils/chromecast-sender";
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
import EnteButton from "@ente/shared/components/EnteButton";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import SingleInputForm, {
type SingleInputFormProps,
} from "@ente/shared/components/SingleInputForm";
import castGateway from "@ente/shared/network/cast";
import { Link, Typography } from "@mui/material";
import { Button, Link, Stack, Typography } from "@mui/material";
import { t } from "i18next";
import { useEffect, useState } from "react";
import { Trans } from "react-i18next";
import { v4 as uuidv4 } from "uuid";
import { loadSender } from "../../utils/useCastSender";

interface Props {
show: boolean;
onHide: () => void;
currentCollection: Collection;
interface AlbumCastDialogProps {
/** If `true`, the dialog is shown. */
open: boolean;
/** Callback fired when the dialog wants to be closed. */
onClose: () => void;
/** The collection that we want to cast. */
collection: Collection;
}

enum AlbumCastError {
TV_NOT_FOUND = "tv_not_found",
}

declare global {
interface Window {
chrome: any;
}
}

export default function AlbumCastDialog({
show,
onHide,
currentCollection,
}: Props) {
/**
* A dialog that shows various options that the user has for casting an album.
*/
export const AlbumCastDialog: React.FC<AlbumCastDialogProps> = ({
open,
onClose,
collection,
}) => {
const [view, setView] = useState<
"choose" | "auto" | "pin" | "auto-cast-error"
>("choose");

const [browserCanCast, setBrowserCanCast] = useState(false);
// Make API call on component mount

// Make API call to clear all previous sessions on component mount.
useEffect(() => {
castGateway.revokeAllTokens();

setBrowserCanCast(!!window.chrome);
setBrowserCanCast(typeof window["chrome"] !== "undefined");
}, []);

const onSubmit: SingleInputFormProps["callback"] = async (
Expand All @@ -55,55 +50,47 @@ export default function AlbumCastDialog({
) => {
try {
await doCast(value.trim());
onHide();
onClose();
} catch (e) {
const error = e as Error;
let fieldError: string;
switch (error.message) {
case AlbumCastError.TV_NOT_FOUND:
fieldError = t("tv_not_found");
break;
default:
fieldError = t("UNKNOWN_ERROR");
break;
if (e instanceof Error && e.message == "tv-not-found") {
setFieldError(t("tv_not_found"));
} else {
setFieldError(t("UNKNOWN_ERROR"));
}

setFieldError(fieldError);
}
};

const doCast = async (pin: string) => {
// does the TV exist? have they advertised their existence?
// Does the TV exist? have they advertised their existence?
const tvPublicKeyB64 = await castGateway.getPublicKey(pin);
if (!tvPublicKeyB64) {
throw new Error(AlbumCastError.TV_NOT_FOUND);
throw new Error("tv-not-found");
}
// generate random uuid string

// Generate random id.
const castToken = uuidv4();

// ok, they exist. let's give them the good stuff.
// Ok, they exist. let's give them the good stuff.
const payload = JSON.stringify({
castToken: castToken,
collectionID: currentCollection.id,
collectionKey: currentCollection.key,
collectionID: collection.id,
collectionKey: collection.key,
});
const encryptedPayload = await boxSeal(btoa(payload), tvPublicKeyB64);

// hey TV, we acknowlege you!
// Hey TV, we acknowlege you!
await castGateway.publishCastPayload(
pin,
encryptedPayload,
currentCollection.id,
collection.id,
castToken,
);
};

useEffect(() => {
if (view === "auto") {
loadSender().then(async (sender) => {
const { cast } = sender;

const instance = await cast.framework.CastContext.getInstance();
loadCast().then(async (cast) => {
const instance = cast.framework.CastContext.getInstance();
try {
await instance.requestSession();
} catch (e) {
Expand All @@ -123,105 +110,80 @@ export default function AlbumCastDialog({
doCast(code)
.then(() => {
setView("choose");
onHide();
onClose();
})
.catch((e) => {
setView("auto-cast-error");
log.error("Error casting to TV", e);
setView("auto-cast-error");
});
}
},
);

const collectionID = currentCollection.id;
const collectionID = collection.id;
session
.sendMessage("urn:x-cast:pair-request", { collectionID })
.then(() => {
log.debug(() => "Message sent successfully");
})
.catch((e) => {
log.error("Error sending message", e);
log.debug(() => "urn:x-cast:pair-request sent");
});
});
}
}, [view]);

useEffect(() => {
if (show) {
castGateway.revokeAllTokens();
}
}, [show]);
if (open) castGateway.revokeAllTokens();
}, [open]);

return (
<DialogBoxV2
open={open}
onClose={onClose}
attributes={{ title: t("cast_album_to_tv") }}
sx={{ zIndex: 1600 }}
open={show}
onClose={onHide}
attributes={{
title: t("cast_album_to_tv"),
}}
>
{view === "choose" && (
<>
{view == "choose" && (
<Stack sx={{ py: 1, gap: 4 }}>
{browserCanCast && (
<>
<Stack sx={{ gap: 2 }}>
<Typography color={"text.muted"}>
{t("cast_auto_pair_description")}
</Typography>

<EnteButton
style={{
marginBottom: "1rem",
}}
onClick={() => {
setView("auto");
}}
>
<Button onClick={() => setView("auto")}>
{t("cast_auto_pair")}
</EnteButton>
</>
</Button>
</Stack>
)}
<Typography color="text.muted">
{t("pair_with_pin_description")}
</Typography>

<EnteButton
onClick={() => {
setView("pin");
}}
>
{t("pair_with_pin")}
</EnteButton>
</>
<Stack sx={{ gap: 2 }}>
<Typography color="text.muted">
{t("pair_with_pin_description")}
</Typography>
<Button onClick={() => setView("pin")}>
{t("pair_with_pin")}
</Button>
</Stack>
</Stack>
)}
{view === "auto" && (
<VerticallyCentered gap="1rem">
<EnteSpinner />
{view == "auto" && (
<Stack sx={{ pt: 1, gap: 3, textAlign: "center" }}>
<div>
<EnteSpinner />
</div>
<Typography>{t("choose_device_from_browser")}</Typography>
<EnteButton
variant="text"
onClick={() => {
setView("choose");
}}
>
<Button color="secondary" onClick={() => setView("choose")}>
{t("GO_BACK")}
</EnteButton>
</VerticallyCentered>
</Button>
</Stack>
)}
{view === "auto-cast-error" && (
<VerticallyCentered gap="1rem">
{view == "auto-cast-error" && (
<Stack sx={{ pt: 1, gap: 3, textAlign: "center" }}>
<Typography>{t("cast_auto_pair_failed")}</Typography>
<EnteButton
variant="text"
onClick={() => {
setView("choose");
}}
>
<Button color="secondary" onClick={() => setView("choose")}>
{t("GO_BACK")}
</EnteButton>
</VerticallyCentered>
</Button>
</Stack>
)}
{view === "pin" && (
{view == "pin" && (
<>
<Typography>
<Trans
Expand All @@ -246,16 +208,11 @@ export default function AlbumCastDialog({
buttonText={t("pair_device_to_tv")}
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}
/>
<EnteButton
variant="text"
onClick={() => {
setView("choose");
}}
>
<Button variant="text" onClick={() => setView("choose")}>
{t("GO_BACK")}
</EnteButton>
</Button>
</>
)}
</DialogBoxV2>
);
}
};
Loading

0 comments on commit dae0492

Please sign in to comment.