Change go to page + add Vercel Analytics integration
This commit is contained in:
parent
08480f07ed
commit
b56584cbed
6 changed files with 87 additions and 26 deletions
|
|
@ -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
39
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue