feat: cast pairing page

This commit is contained in:
httpjamesm 2023-11-15 19:12:40 -05:00
parent ee4b091f01
commit caf7dc77ae
No known key found for this signature in database
8 changed files with 230 additions and 416 deletions

View file

@ -0,0 +1,3 @@
<svg width="43" height="13" viewBox="0 0 43 13" fill="#fff" xmlns="http://www.w3.org/2000/svg">
<path d="M6.102 12.144C4.998 12.144 4.026 11.928 3.186 11.496C2.358 11.064 1.716 10.476 1.26 9.732C0.804 8.976 0.576 8.118 0.576 7.158C0.576 6.186 0.798 5.328 1.242 4.584C1.698 3.828 2.316 3.24 3.096 2.82C3.876 2.388 4.758 2.172 5.742 2.172C6.69 2.172 7.542 2.376 8.298 2.784C9.066 3.18 9.672 3.756 10.116 4.512C10.56 5.256 10.782 6.15 10.782 7.194C10.782 7.302 10.776 7.428 10.764 7.572C10.752 7.704 10.74 7.83 10.728 7.95H2.862V6.312H9.252L8.172 6.798C8.172 6.294 8.07 5.856 7.866 5.484C7.662 5.112 7.38 4.824 7.02 4.62C6.66 4.404 6.24 4.296 5.76 4.296C5.28 4.296 4.854 4.404 4.482 4.62C4.122 4.824 3.84 5.118 3.636 5.502C3.432 5.874 3.33 6.318 3.33 6.834V7.266C3.33 7.794 3.444 8.262 3.672 8.67C3.912 9.066 4.242 9.372 4.662 9.588C5.094 9.792 5.598 9.894 6.174 9.894C6.69 9.894 7.14 9.816 7.524 9.66C7.92 9.504 8.28 9.27 8.604 8.958L10.098 10.578C9.654 11.082 9.096 11.472 8.424 11.748C7.752 12.012 6.978 12.144 6.102 12.144ZM18.5375 2.172C19.3055 2.172 19.9895 2.328 20.5895 2.64C21.2015 2.94 21.6815 3.408 22.0295 4.044C22.3775 4.668 22.5515 5.472 22.5515 6.456V12H19.7435V6.888C19.7435 6.108 19.5695 5.532 19.2215 5.16C18.8855 4.788 18.4055 4.602 17.7815 4.602C17.3375 4.602 16.9355 4.698 16.5755 4.89C16.2275 5.07 15.9515 5.352 15.7475 5.736C15.5555 6.12 15.4595 6.612 15.4595 7.212V12H12.6515V2.316H15.3335V4.998L14.8295 4.188C15.1775 3.54 15.6755 3.042 16.3235 2.694C16.9715 2.346 17.7095 2.172 18.5375 2.172ZM29.0568 12.144C27.9168 12.144 27.0288 11.856 26.3928 11.28C25.7568 10.692 25.4388 9.822 25.4388 8.67V0.174H28.2468V8.634C28.2468 9.042 28.3548 9.36 28.5708 9.588C28.7868 9.804 29.0808 9.912 29.4528 9.912C29.8968 9.912 30.2748 9.792 30.5868 9.552L31.3428 11.532C31.0548 11.736 30.7068 11.892 30.2988 12C29.9028 12.096 29.4888 12.144 29.0568 12.144ZM23.9448 4.692V2.532H30.6588V4.692H23.9448ZM37.4262 12.144C36.3222 12.144 35.3502 11.928 34.5102 11.496C33.6822 11.064 33.0402 10.476 32.5842 9.732C32.1282 8.976 31.9002 8.118 31.9002 7.158C31.9002 6.186 32.1222 5.328 32.5662 4.584C33.0222 3.828 33.6402 3.24 34.4202 2.82C35.2002 2.388 36.0822 2.172 37.0662 2.172C38.0142 2.172 38.8662 2.376 39.6222 2.784C40.3902 3.18 40.9962 3.756 41.4402 4.512C41.8842 5.256 42.1062 6.15 42.1062 7.194C42.1062 7.302 42.1002 7.428 42.0882 7.572C42.0762 7.704 42.0642 7.83 42.0522 7.95H34.1862V6.312H40.5762L39.4962 6.798C39.4962 6.294 39.3942 5.856 39.1902 5.484C38.9862 5.112 38.7042 4.824 38.3442 4.62C37.9842 4.404 37.5642 4.296 37.0842 4.296C36.6042 4.296 36.1782 4.404 35.8062 4.62C35.4462 4.824 35.1642 5.118 34.9602 5.502C34.7562 5.874 34.6542 6.318 34.6542 6.834V7.266C34.6542 7.794 34.7682 8.262 34.9962 8.67C35.2362 9.066 35.5662 9.372 35.9862 9.588C36.4182 9.792 36.9222 9.894 37.4982 9.894C38.0142 9.894 38.4642 9.816 38.8482 9.66C39.2442 9.504 39.6042 9.27 39.9282 8.958L41.4222 10.578C40.9782 11.082 40.4202 11.472 39.7482 11.748C39.0762 12.012 38.3022 12.144 37.4262 12.144Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,5 +1,17 @@
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import { Inter } from 'next/font/google';
import 'styles/global.css';
const inter = Inter({ subsets: ['latin'] });
export default function App({ Component, pageProps }: AppProps) { export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />; return (
<main
className={inter.className}
style={{
display: 'contents',
}}>
<Component {...pageProps} />
</main>
);
} }

View file

@ -2,11 +2,20 @@ import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() { export default function Document() {
return ( return (
<Html lang="en"> <Html
lang="en"
style={{
height: '100%',
width: '100%',
}}>
<Head /> <Head />
<body <body
style={{ style={{
height: '100%',
width: '100%',
margin: 0, margin: 0,
backgroundColor: 'black',
color: 'white',
}}> }}>
<Main /> <Main />
<NextScript /> <NextScript />

View file

@ -1,95 +1,128 @@
// import { Inter } from 'next/font/google';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { syncCollections } from 'services/collectionService';
import { syncFiles } from 'services/fileService';
import { EnteFile } from 'types/file';
import { downloadFileAsBlob } from 'utils/file';
// const inter = Inter({ subsets: ['latin'] }); const colourPool = [
'#87CEFA', // Light Blue
'#90EE90', // Light Green
'#F08080', // Light Coral
'#FFFFE0', // Light Yellow
'#FFB6C1', // Light Pink
'#E0FFFF', // Light Cyan
'#FAFAD2', // Light Goldenrod
'#87CEFA', // Light Sky Blue
'#D3D3D3', // Light Gray
'#B0C4DE', // Light Steel Blue
'#FFA07A', // Light Salmon
'#20B2AA', // Light Sea Green
'#778899', // Light Slate Gray
'#AFEEEE', // Light Turquoise
'#7A58C1', // Light Violet
'#FFA500', // Light Orange
'#A0522D', // Light Brown
'#9370DB', // Light Purple
'#008080', // Light Teal
'#808000', // Light Olive
];
export default function Home() { export default function PairingMode() {
const [collectionFiles, setCollectionFiles] = useState<EnteFile[]>([]); // Function to generate cryptographically secure data
function generateSecureData(length: number): Uint8Array {
const [currentFile, setCurrentFile] = useState<EnteFile | undefined>( const array = new Uint8Array(length);
undefined window.crypto.getRandomValues(array);
); // Modulo operation to ensure each byte is a single digit
for (let i = 0; i < length; i++) {
const init = async () => { array[i] = array[i] % 10;
const collections = await syncCollections();
// get requested collection id from fragment (this is temporary and will be changed during cast)
const requestedCollectionID = window.location.hash.slice(1);
const files = await syncFiles('normal', collections, () => {});
if (requestedCollectionID) {
const collectionFiles = files.filter(
(file) => file.collectionID === Number(requestedCollectionID)
);
setCollectionFiles(collectionFiles);
} }
}; return array;
}
// Function to convert data into digits
function convertDataToDigits(data: Uint8Array): string {
let result = '';
for (let i = 0; i < data.length; i++) {
result += data[i].toString();
}
return result;
}
const [digits, setDigits] = useState<string[]>([]);
useEffect(() => { useEffect(() => {
init(); setDigits(convertDataToDigits(generateSecureData(10)).split(''));
}, []); }, []);
useEffect(() => {
// create interval to change slide
const interval = setInterval(() => {
// set the currentFile to the next file in the collection for the slideshow
const currentIndex = collectionFiles.findIndex(
(file) => file.id === currentFile?.id
);
const nextIndex = (currentIndex + 1) % collectionFiles.length;
const nextFile = collectionFiles[nextIndex];
setCurrentFile(nextFile);
}, 5000);
return () => {
clearInterval(interval);
};
}, [collectionFiles, currentFile]);
const [renderableFileURL, setRenderableFileURL] = useState<string>('');
const getRenderableFileURL = async () => {
const blob = await downloadFileAsBlob(currentFile as EnteFile);
const url = URL.createObjectURL(blob);
setRenderableFileURL(url);
};
useEffect(() => {
if (currentFile) {
console.log(currentFile);
getRenderableFileURL();
}
}, [currentFile]);
return ( return (
<> <>
<div <div
style={{ style={{
width: '100vw', height: '100%',
height: '100vh',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
backgroundColor: 'black',
}}> }}>
<img <div
src={renderableFileURL}
style={{ style={{
maxWidth: '100%', textAlign: 'center',
maxHeight: '100%', display: 'flex',
}} flexDirection: 'column',
/> alignItems: 'center',
}}>
<img width={150} src="/images/ente.svg" />
<h1
style={{
fontWeight: 'normal',
}}>
Enter this code on <b>ente</b> to pair this TV
</h1>
<table
style={{
fontSize: '4rem',
fontWeight: 'bold',
fontFamily: 'monospace',
display: 'flex',
}}>
{digits.map((digit, i) => (
<tr
key={i}
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '0.5rem',
// alternating background
backgroundColor:
i % 2 === 0 ? '#2e2e2e' : '#5e5e5e',
}}>
<span
style={{
color: colourPool[
i % colourPool.length
],
}}>
{digit}
</span>
<span
style={{
fontSize: '1rem',
}}>
{i + 1}
</span>
</tr>
))}
</table>
<p
style={{
fontSize: '1.2rem',
}}>
Visit{' '}
<span
style={{
color: '#87CEFA',
fontWeight: 'bold',
}}>
ente.io/cast
</span>{' '}
for help
</p>
</div>
</div> </div>
</> </>
); );

View file

@ -0,0 +1,93 @@
// import { Inter } from 'next/font/google';
import { useEffect, useState } from 'react';
import { syncCollections } from 'services/collectionService';
import { syncFiles } from 'services/fileService';
import { EnteFile } from 'types/file';
import { downloadFileAsBlob } from 'utils/file';
export default function Slideshow() {
const [collectionFiles, setCollectionFiles] = useState<EnteFile[]>([]);
const [currentFile, setCurrentFile] = useState<EnteFile | undefined>(
undefined
);
const init = async () => {
const collections = await syncCollections();
// get requested collection id from fragment (this is temporary and will be changed during cast)
const requestedCollectionID = window.location.hash.slice(1);
const files = await syncFiles('normal', collections, () => {});
if (requestedCollectionID) {
const collectionFiles = files.filter(
(file) => file.collectionID === Number(requestedCollectionID)
);
setCollectionFiles(collectionFiles);
}
};
useEffect(() => {
init();
}, []);
useEffect(() => {
// create interval to change slide
const interval = setInterval(() => {
// set the currentFile to the next file in the collection for the slideshow
const currentIndex = collectionFiles.findIndex(
(file) => file.id === currentFile?.id
);
const nextIndex = (currentIndex + 1) % collectionFiles.length;
const nextFile = collectionFiles[nextIndex];
setCurrentFile(nextFile);
}, 5000);
return () => {
clearInterval(interval);
};
}, [collectionFiles, currentFile]);
const [renderableFileURL, setRenderableFileURL] = useState<string>('');
const getRenderableFileURL = async () => {
const blob = await downloadFileAsBlob(currentFile as EnteFile);
const url = URL.createObjectURL(blob);
setRenderableFileURL(url);
};
useEffect(() => {
if (currentFile) {
console.log(currentFile);
getRenderableFileURL();
}
}, [currentFile]);
return (
<>
<div
style={{
width: '100vw',
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}>
<img
src={renderableFileURL}
style={{
maxWidth: '100%',
maxHeight: '100%',
}}
/>
</div>
</>
);
}

View file

@ -1,229 +0,0 @@
.main {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 6rem;
min-height: 100vh;
}
.description {
display: inherit;
justify-content: inherit;
align-items: inherit;
font-size: 0.85rem;
max-width: var(--max-width);
width: 100%;
z-index: 2;
font-family: var(--font-mono);
}
.description a {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
}
.description p {
position: relative;
margin: 0;
padding: 1rem;
background-color: rgba(var(--callout-rgb), 0.5);
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
border-radius: var(--border-radius);
}
.code {
font-weight: 700;
font-family: var(--font-mono);
}
.grid {
display: grid;
grid-template-columns: repeat(4, minmax(25%, auto));
max-width: 100%;
width: var(--max-width);
}
.card {
padding: 1rem 1.2rem;
border-radius: var(--border-radius);
background: rgba(var(--card-rgb), 0);
border: 1px solid rgba(var(--card-border-rgb), 0);
transition: background 200ms, border 200ms;
}
.card span {
display: inline-block;
transition: transform 200ms;
}
.card h2 {
font-weight: 600;
margin-bottom: 0.7rem;
}
.card p {
margin: 0;
opacity: 0.6;
font-size: 0.9rem;
line-height: 1.5;
max-width: 30ch;
}
.center {
display: flex;
justify-content: center;
align-items: center;
position: relative;
padding: 4rem 0;
}
.center::before {
background: var(--secondary-glow);
border-radius: 50%;
width: 480px;
height: 360px;
margin-left: -400px;
}
.center::after {
background: var(--primary-glow);
width: 240px;
height: 180px;
z-index: -1;
}
.center::before,
.center::after {
content: '';
left: 50%;
position: absolute;
filter: blur(45px);
transform: translateZ(0);
}
.logo {
position: relative;
}
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
.card:hover {
background: rgba(var(--card-rgb), 0.1);
border: 1px solid rgba(var(--card-border-rgb), 0.15);
}
.card:hover span {
transform: translateX(4px);
}
}
@media (prefers-reduced-motion) {
.card:hover span {
transform: none;
}
}
/* Mobile */
@media (max-width: 700px) {
.content {
padding: 4rem;
}
.grid {
grid-template-columns: 1fr;
margin-bottom: 120px;
max-width: 320px;
text-align: center;
}
.card {
padding: 1rem 2.5rem;
}
.card h2 {
margin-bottom: 0.5rem;
}
.center {
padding: 8rem 0 6rem;
}
.center::before {
transform: none;
height: 300px;
}
.description {
font-size: 0.8rem;
}
.description a {
padding: 1rem;
}
.description p,
.description div {
display: flex;
justify-content: center;
position: fixed;
width: 100%;
}
.description p {
align-items: center;
inset: 0 0 auto;
padding: 2rem 1rem 1.4rem;
border-radius: 0;
border: none;
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
background: linear-gradient(
to bottom,
rgba(var(--background-start-rgb), 1),
rgba(var(--callout-rgb), 0.5)
);
background-clip: padding-box;
backdrop-filter: blur(24px);
}
.description div {
align-items: flex-end;
pointer-events: none;
inset: auto 0 0;
padding: 2rem;
height: 200px;
background: linear-gradient(
to bottom,
transparent 0%,
rgb(var(--background-end-rgb)) 40%
);
z-index: 1;
}
}
/* Tablet and Smaller Desktop */
@media (min-width: 701px) and (max-width: 1120px) {
.grid {
grid-template-columns: repeat(2, 50%);
}
}
@media (prefers-color-scheme: dark) {
.vercelLogo {
filter: invert(1);
}
.logo {
filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
}
}
@keyframes rotate {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}

View file

@ -0,0 +1,3 @@
#__next {
height: 100%;
}

View file

@ -1,110 +0,0 @@
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
--primary-glow: conic-gradient(
from 180deg at 50% 50%,
#16abff33 0deg,
#0885ff33 55deg,
#54d6ff33 120deg,
#0071ff33 160deg,
transparent 360deg
);
--secondary-glow: radial-gradient(
rgba(255, 255, 255, 1),
rgba(255, 255, 255, 0)
);
--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
--tile-border: conic-gradient(
#00000080,
#00000040,
#00000030,
#00000020,
#00000010,
#00000010,
#00000080
);
--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
--card-rgb: 180, 185, 188;
--card-border-rgb: 131, 134, 135;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--primary-glow: radial-gradient(
rgba(1, 65, 255, 0.4),
rgba(1, 65, 255, 0)
);
--secondary-glow: linear-gradient(
to bottom right,
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0.3)
);
--tile-start-rgb: 2, 13, 46;
--tile-end-rgb: 2, 5, 19;
--tile-border: conic-gradient(
#ffffff80,
#ffffff40,
#ffffff30,
#ffffff20,
#ffffff10,
#ffffff10,
#ffffff80
);
--callout-rgb: 20, 20, 20;
--callout-border-rgb: 108, 108, 108;
--card-rgb: 100, 100, 100;
--card-border-rgb: 200, 200, 200;
}
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}