Change go to page + add Vercel Analytics integration

This commit is contained in:
Richard Roberson 2025-06-30 12:10:56 -06:00
parent 08480f07ed
commit b56584cbed
6 changed files with 87 additions and 26 deletions

View file

@ -1,11 +1,9 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
turbo: {
resolveAlias: {
canvas: './empty-module.ts',
},
turbopack: {
resolveAlias: {
canvas: './empty-module.ts',
},
},
};

39
package-lock.json generated
View file

@ -12,6 +12,7 @@
"@types/howler": "^2.2.12",
"@types/string-similarity": "^4.0.2",
"@types/uuid": "^10.0.0",
"@vercel/analytics": "^1.5.0",
"compromise": "^14.14.4",
"core-js": "^3.41.0",
"howler": "^2.2.4",
@ -1987,6 +1988,44 @@
"win32"
]
},
"node_modules/@vercel/analytics": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.5.0.tgz",
"integrity": "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==",
"license": "MPL-2.0",
"peerDependencies": {
"@remix-run/react": "^2",
"@sveltejs/kit": "^1 || ^2",
"next": ">= 13",
"react": "^18 || ^19 || ^19.0.0-rc",
"svelte": ">= 4",
"vue": "^3",
"vue-router": "^4"
},
"peerDependenciesMeta": {
"@remix-run/react": {
"optional": true
},
"@sveltejs/kit": {
"optional": true
},
"next": {
"optional": true
},
"react": {
"optional": true
},
"svelte": {
"optional": true
},
"vue": {
"optional": true
},
"vue-router": {
"optional": true
}
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.7.13",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz",

View file

@ -13,6 +13,7 @@
"@types/howler": "^2.2.12",
"@types/string-similarity": "^4.0.2",
"@types/uuid": "^10.0.0",
"@vercel/analytics": "^1.5.0",
"compromise": "^14.14.4",
"core-js": "^3.41.0",
"howler": "^2.2.4",

View file

@ -4,6 +4,7 @@ import { Providers } from "@/app/providers";
import { Metadata } from "next";
import { Footer } from "@/components/Footer";
import { Toaster } from 'react-hot-toast';
import { Analytics } from "@vercel/analytics/next"
export const metadata: Metadata = {
title: "OpenReader WebUI",
@ -59,6 +60,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
<div className="relative max-w-6xl mx-auto align-center">
<div className="bg-base rounded-lg shadow-lg">
{children}
<Analytics />
</div>
{!isDev && <Footer />}
</div>

View file

@ -21,7 +21,10 @@ export function Footer() {
</PopoverButton>
<PopoverPanel anchor="top" className="bg-base p-4 rounded-lg shadow-lg w-64">
<p>Documents are uploaded to your local browser cache.</p>
<p className='mt-3'>Each sentence of the document you are viewing is sent to my Kokoro-FastAPI server for audio generation, no requests or data is collected.</p>
<p className='mt-3'>Each sentence of the document you are viewing is sent to my Kokoro-FastAPI server for audio generation.</p>
<p className='mt-3'>The audio is streamed back to your browser and played in real-time.</p>
{/* Vercel analytics disclaimer */}
<p className='mt-3'>This site uses Vercel Analytics to collect anonymous usage data to help improve the service.</p>
</PopoverPanel>
</Popover>
<span className='w-full sm:w-fit'></span>

View file

@ -1,6 +1,6 @@
'use client';
import { Button } from '@headlessui/react';
import { Button, Popover, PopoverButton, PopoverPanel } from '@headlessui/react';
import { useState, useEffect, useRef } from 'react';
export const Navigator = ({ currentPage, numPages, skipToLocation }: {
@ -8,7 +8,7 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
numPages: number | undefined;
skipToLocation: (location: string | number, shouldPause?: boolean) => void;
}) => {
const [inputValue, setInputValue] = useState(currentPage.toString());
const [inputValue, setInputValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
@ -22,6 +22,7 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
};
const handleInputConfirm = () => {
if (inputValue === '') return; // Don't do anything if input is empty
let page = parseInt(inputValue, 10);
if (isNaN(page)) return;
const maxPage = numPages || 1;
@ -29,9 +30,8 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
if (page > maxPage) page = maxPage;
if (page !== currentPage) {
skipToLocation(page, true);
} else {
setInputValue(page.toString()); // reset input if unchanged
}
setInputValue(''); // Clear input after confirming
};
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
@ -41,6 +41,10 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
}
};
const handlePopoverOpen = () => {
setInputValue(''); // Clear input when popup opens
};
return (
<div className="flex items-center space-x-1">
{/* Page back */}
@ -55,22 +59,36 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
</svg>
</Button>
{/* Page number input */}
<div className="bg-offbase px-2 py-0.5 rounded-full flex items-center">
<input
ref={inputRef}
type="text"
inputMode="numeric"
pattern="[0-9]*"
className="w-6 text-xs text-accent bg-transparent outline-none appearance-none text-right"
value={inputValue}
onChange={handleInputChange}
onBlur={handleInputConfirm}
onKeyDown={handleInputKeyDown}
aria-label="Page number"
/>
<span className="w-6 text-xs ml-1">/ {numPages || 1}</span>
</div>
{/* Page number popup */}
<Popover className="relative">
<PopoverButton
className="bg-offbase px-2 py-0.5 rounded-full focus:outline-none cursor-pointer hover:bg-offbase/80"
onClick={handlePopoverOpen}
>
<p className="text-xs whitespace-nowrap">
{currentPage} / {numPages || 1}
</p>
</PopoverButton>
<PopoverPanel anchor="top" className="absolute z-50 bg-base p-3 rounded-md shadow-lg border border-offbase">
<div className="flex flex-col space-y-2">
<div className="text-xs font-medium text-foreground">Go to page</div>
<input
ref={inputRef}
type="text"
inputMode="numeric"
pattern="[0-9]*"
className="w-20 px-2 py-1 text-xs text-accent bg-offbase rounded border-none outline-none appearance-none text-center"
value={inputValue}
onChange={handleInputChange}
onBlur={handleInputConfirm}
onKeyDown={handleInputKeyDown}
placeholder={currentPage.toString()}
aria-label="Page number"
/>
<div className="text-xs text-foreground/70 text-center">of {numPages || 1}</div>
</div>
</PopoverPanel>
</Popover>
{/* Page forward */}
<Button