mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
Try adding vercel analytics
This commit is contained in:
parent
e0b59c011b
commit
677b53b172
11 changed files with 438 additions and 0 deletions
|
@ -36,6 +36,7 @@
|
||||||
"@scure/bip39": "^1.2.2",
|
"@scure/bip39": "^1.2.2",
|
||||||
"@sozialhelden/ietf-language-tags": "^5.4.2",
|
"@sozialhelden/ietf-language-tags": "^5.4.2",
|
||||||
"@types/node-forge": "^1.3.10",
|
"@types/node-forge": "^1.3.10",
|
||||||
|
"@vercel/analytics": "^1.2.2",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"core-js": "^3.34.0",
|
"core-js": "^3.34.0",
|
||||||
"detect-browser": "^5.3.0",
|
"detect-browser": "^5.3.0",
|
||||||
|
|
|
@ -42,6 +42,9 @@ dependencies:
|
||||||
'@types/node-forge':
|
'@types/node-forge':
|
||||||
specifier: ^1.3.10
|
specifier: ^1.3.10
|
||||||
version: 1.3.10
|
version: 1.3.10
|
||||||
|
'@vercel/analytics':
|
||||||
|
specifier: ^1.2.2
|
||||||
|
version: 1.2.2(react@18.2.0)
|
||||||
classnames:
|
classnames:
|
||||||
specifier: ^2.3.2
|
specifier: ^2.3.2
|
||||||
version: 2.3.2
|
version: 2.3.2
|
||||||
|
@ -2488,6 +2491,21 @@ packages:
|
||||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@vercel/analytics@1.2.2(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-X0rctVWkQV1e5Y300ehVNqpOfSOufo7ieA5PIdna8yX/U7Vjz0GFsGf4qvAhxV02uQ2CVt7GYcrFfddXXK2Y4A==}
|
||||||
|
peerDependencies:
|
||||||
|
next: '>= 13'
|
||||||
|
react: ^18 || ^19
|
||||||
|
peerDependenciesMeta:
|
||||||
|
next:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
server-only: 0.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vitejs/plugin-react@4.2.1(vite@5.0.12):
|
/@vitejs/plugin-react@4.2.1(vite@5.0.12):
|
||||||
resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==}
|
resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
@ -6172,6 +6190,10 @@ packages:
|
||||||
randombytes: 2.1.0
|
randombytes: 2.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/server-only@0.0.1:
|
||||||
|
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/set-cookie-parser@2.6.0:
|
/set-cookie-parser@2.6.0:
|
||||||
resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
|
resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Analytics } from "@vercel/analytics/react";
|
||||||
import { ReactElement, Suspense, lazy, useEffect } from "react";
|
import { ReactElement, Suspense, lazy, useEffect } from "react";
|
||||||
import { lazyWithPreload } from "react-lazy-with-preload";
|
import { lazyWithPreload } from "react-lazy-with-preload";
|
||||||
import {
|
import {
|
||||||
|
@ -155,6 +156,7 @@ function App() {
|
||||||
) : null}
|
) : null}
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
<Analytics />
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
162
src/utils/setup/App.tsx
Normal file
162
src/utils/setup/App.tsx
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
import { ReactElement, Suspense, lazy, useEffect } from "react";
|
||||||
|
import { lazyWithPreload } from "react-lazy-with-preload";
|
||||||
|
import {
|
||||||
|
Navigate,
|
||||||
|
Route,
|
||||||
|
Routes,
|
||||||
|
useLocation,
|
||||||
|
useNavigate,
|
||||||
|
useParams,
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
|
import { convertLegacyUrl, isLegacyUrl } from "@/backend/metadata/getmeta";
|
||||||
|
import { generateQuickSearchMediaUrl } from "@/backend/metadata/tmdb";
|
||||||
|
import { useOnlineListener } from "@/hooks/usePing";
|
||||||
|
import { AboutPage } from "@/pages/About";
|
||||||
|
import { AdminPage } from "@/pages/admin/AdminPage";
|
||||||
|
import VideoTesterView from "@/pages/developer/VideoTesterView";
|
||||||
|
import { DmcaPage, shouldHaveDmcaPage } from "@/pages/Dmca";
|
||||||
|
import { NotFoundPage } from "@/pages/errors/NotFoundPage";
|
||||||
|
import { HomePage } from "@/pages/HomePage";
|
||||||
|
import { LoginPage } from "@/pages/Login";
|
||||||
|
import { OnboardingPage } from "@/pages/onboarding/Onboarding";
|
||||||
|
import { OnboardingExtensionPage } from "@/pages/onboarding/OnboardingExtension";
|
||||||
|
import { OnboardingProxyPage } from "@/pages/onboarding/OnboardingProxy";
|
||||||
|
import { RegisterPage } from "@/pages/Register";
|
||||||
|
import { Layout } from "@/setup/Layout";
|
||||||
|
import { useHistoryListener } from "@/stores/history";
|
||||||
|
import { LanguageProvider } from "@/stores/language";
|
||||||
|
|
||||||
|
const DeveloperPage = lazy(() => import("@/pages/DeveloperPage"));
|
||||||
|
const TestView = lazy(() => import("@/pages/developer/TestView"));
|
||||||
|
const PlayerView = lazyWithPreload(() => import("@/pages/PlayerView"));
|
||||||
|
const SettingsPage = lazyWithPreload(() => import("@/pages/Settings"));
|
||||||
|
|
||||||
|
PlayerView.preload();
|
||||||
|
SettingsPage.preload();
|
||||||
|
|
||||||
|
function LegacyUrlView({ children }: { children: ReactElement }) {
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const url = location.pathname;
|
||||||
|
if (!isLegacyUrl(url)) return;
|
||||||
|
convertLegacyUrl(location.pathname).then((convertedUrl) => {
|
||||||
|
navigate(convertedUrl ?? "/", { replace: true });
|
||||||
|
});
|
||||||
|
}, [location.pathname, navigate]);
|
||||||
|
|
||||||
|
if (isLegacyUrl(location.pathname)) return null;
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
function QuickSearch() {
|
||||||
|
const { query } = useParams<{ query: string }>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (query) {
|
||||||
|
generateQuickSearchMediaUrl(query).then((url) => {
|
||||||
|
navigate(url ?? "/", { replace: true });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
navigate("/", { replace: true });
|
||||||
|
}
|
||||||
|
}, [query, navigate]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function QueryView() {
|
||||||
|
const { query } = useParams<{ query: string }>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (query) {
|
||||||
|
navigate(`/browse/${query}`, { replace: true });
|
||||||
|
} else {
|
||||||
|
navigate("/", { replace: true });
|
||||||
|
}
|
||||||
|
}, [query, navigate]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
useHistoryListener();
|
||||||
|
useOnlineListener();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<LanguageProvider />
|
||||||
|
<Routes>
|
||||||
|
{/* functional routes */}
|
||||||
|
<Route path="/s/:query" element={<QuickSearch />} />
|
||||||
|
<Route path="/search/:type" element={<Navigate to="/browse" />} />
|
||||||
|
<Route path="/search/:type/:query?" element={<QueryView />} />
|
||||||
|
|
||||||
|
{/* pages */}
|
||||||
|
<Route
|
||||||
|
path="/media/:media"
|
||||||
|
element={
|
||||||
|
<LegacyUrlView>
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<PlayerView />
|
||||||
|
</Suspense>
|
||||||
|
</LegacyUrlView>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/media/:media/:season/:episode"
|
||||||
|
element={
|
||||||
|
<LegacyUrlView>
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<PlayerView />
|
||||||
|
</Suspense>
|
||||||
|
</LegacyUrlView>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/browse/:query?" element={<HomePage />} />
|
||||||
|
<Route path="/" element={<HomePage />} />
|
||||||
|
<Route path="/register" element={<RegisterPage />} />
|
||||||
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
<Route path="/about" element={<AboutPage />} />
|
||||||
|
<Route path="/onboarding" element={<OnboardingPage />} />
|
||||||
|
<Route
|
||||||
|
path="/onboarding/extension"
|
||||||
|
element={<OnboardingExtensionPage />}
|
||||||
|
/>
|
||||||
|
<Route path="/onboarding/proxy" element={<OnboardingProxyPage />} />
|
||||||
|
|
||||||
|
{shouldHaveDmcaPage() ? (
|
||||||
|
<Route path="/dmca" element={<DmcaPage />} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{/* Settings page */}
|
||||||
|
<Route
|
||||||
|
path="/settings"
|
||||||
|
element={
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<SettingsPage />
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* admin routes */}
|
||||||
|
<Route path="/admin" element={<AdminPage />} />
|
||||||
|
|
||||||
|
{/* other */}
|
||||||
|
<Route path="/dev" element={<DeveloperPage />} />
|
||||||
|
<Route path="/dev/video" element={<VideoTesterView />} />
|
||||||
|
{/* developer routes that can abuse workers are disabled in production */}
|
||||||
|
{process.env.NODE_ENV === "development" ? (
|
||||||
|
<Route path="/dev/test" element={<TestView />} />
|
||||||
|
) : null}
|
||||||
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
|
</Routes>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
25
src/utils/setup/Layout.tsx
Normal file
25
src/utils/setup/Layout.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
import { useBannerSize, useBannerStore } from "@/stores/banner";
|
||||||
|
import { BannerLocation } from "@/stores/banner/BannerLocation";
|
||||||
|
|
||||||
|
export function Layout(props: { children: ReactNode }) {
|
||||||
|
const bannerSize = useBannerSize();
|
||||||
|
const location = useBannerStore((s) => s.location);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="fixed inset-x-0 z-[1000]">
|
||||||
|
<BannerLocation />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingTop: location === null ? `${bannerSize}px` : "0px",
|
||||||
|
}}
|
||||||
|
className="flex min-h-screen flex-col"
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
30
src/utils/setup/chromecast.ts
Normal file
30
src/utils/setup/chromecast.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
const CHROMECAST_SENDER_SDK =
|
||||||
|
"https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1";
|
||||||
|
|
||||||
|
const callbacks: ((available: boolean) => void)[] = [];
|
||||||
|
let _available: boolean | null = null;
|
||||||
|
|
||||||
|
function init(available: boolean) {
|
||||||
|
_available = available;
|
||||||
|
callbacks.forEach((cb) => cb(available));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isChromecastAvailable(cb: (available: boolean) => void) {
|
||||||
|
if (_available !== null) return cb(_available);
|
||||||
|
callbacks.push(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializeChromecast() {
|
||||||
|
window.__onGCastApiAvailable = (isAvailable) => {
|
||||||
|
init(isAvailable);
|
||||||
|
};
|
||||||
|
|
||||||
|
// add script if doesnt exist yet
|
||||||
|
const exists = !!document.getElementById("chromecast-script");
|
||||||
|
if (!exists) {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.setAttribute("src", CHROMECAST_SENDER_SDK);
|
||||||
|
script.setAttribute("id", "chromecast-script");
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
|
}
|
125
src/utils/setup/config.ts
Normal file
125
src/utils/setup/config.ts
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import {
|
||||||
|
APP_VERSION,
|
||||||
|
BACKEND_URL,
|
||||||
|
DISCORD_LINK,
|
||||||
|
DONATION_LINK,
|
||||||
|
GITHUB_LINK,
|
||||||
|
} from "./constants";
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
APP_VERSION: string;
|
||||||
|
GITHUB_LINK: string;
|
||||||
|
DONATION_LINK: string;
|
||||||
|
DISCORD_LINK: string;
|
||||||
|
DMCA_EMAIL: string;
|
||||||
|
TMDB_READ_API_KEY: string;
|
||||||
|
CORS_PROXY_URL: string;
|
||||||
|
NORMAL_ROUTER: boolean;
|
||||||
|
BACKEND_URL: string;
|
||||||
|
DISALLOWED_IDS: string;
|
||||||
|
TURNSTILE_KEY: string;
|
||||||
|
CDN_REPLACEMENTS: string;
|
||||||
|
HAS_ONBOARDING: string;
|
||||||
|
ONBOARDING_CHROME_EXTENSION_INSTALL_LINK: string;
|
||||||
|
ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK: string;
|
||||||
|
ONBOARDING_PROXY_INSTALL_LINK: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuntimeConfig {
|
||||||
|
APP_VERSION: string;
|
||||||
|
GITHUB_LINK: string;
|
||||||
|
DONATION_LINK: string;
|
||||||
|
DISCORD_LINK: string;
|
||||||
|
DMCA_EMAIL: string | null;
|
||||||
|
TMDB_READ_API_KEY: string;
|
||||||
|
NORMAL_ROUTER: boolean;
|
||||||
|
PROXY_URLS: string[];
|
||||||
|
BACKEND_URL: string;
|
||||||
|
DISALLOWED_IDS: string[];
|
||||||
|
TURNSTILE_KEY: string | null;
|
||||||
|
CDN_REPLACEMENTS: Array<string[]>;
|
||||||
|
HAS_ONBOARDING: boolean;
|
||||||
|
ONBOARDING_CHROME_EXTENSION_INSTALL_LINK: string | null;
|
||||||
|
ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK: string | null;
|
||||||
|
ONBOARDING_PROXY_INSTALL_LINK: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const env: Record<keyof Config, undefined | string> = {
|
||||||
|
TMDB_READ_API_KEY: import.meta.env.VITE_TMDB_READ_API_KEY,
|
||||||
|
APP_VERSION: undefined,
|
||||||
|
GITHUB_LINK: undefined,
|
||||||
|
DONATION_LINK: undefined,
|
||||||
|
DISCORD_LINK: undefined,
|
||||||
|
ONBOARDING_CHROME_EXTENSION_INSTALL_LINK: import.meta.env
|
||||||
|
.VITE_ONBOARDING_CHROME_EXTENSION_INSTALL_LINK,
|
||||||
|
ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK: import.meta.env
|
||||||
|
.VITE_ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK,
|
||||||
|
ONBOARDING_PROXY_INSTALL_LINK: import.meta.env
|
||||||
|
.VITE_ONBOARDING_PROXY_INSTALL_LINK,
|
||||||
|
DMCA_EMAIL: import.meta.env.VITE_DMCA_EMAIL,
|
||||||
|
CORS_PROXY_URL: import.meta.env.VITE_CORS_PROXY_URL,
|
||||||
|
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
||||||
|
BACKEND_URL: import.meta.env.VITE_BACKEND_URL,
|
||||||
|
DISALLOWED_IDS: import.meta.env.VITE_DISALLOWED_IDS,
|
||||||
|
TURNSTILE_KEY: import.meta.env.VITE_TURNSTILE_KEY,
|
||||||
|
CDN_REPLACEMENTS: import.meta.env.VITE_CDN_REPLACEMENTS,
|
||||||
|
HAS_ONBOARDING: import.meta.env.VITE_HAS_ONBOARDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
// loads from different locations, in order: environment (VITE_{KEY}), window (public/config.js)
|
||||||
|
function getKeyValue(key: keyof Config): string | undefined {
|
||||||
|
let windowValue = (window as any)?.__CONFIG__?.[`VITE_${key}`];
|
||||||
|
if (
|
||||||
|
windowValue !== null &&
|
||||||
|
windowValue !== undefined &&
|
||||||
|
windowValue.length === 0
|
||||||
|
)
|
||||||
|
windowValue = undefined;
|
||||||
|
return env[key] ?? windowValue ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKey(key: keyof Config, defaultString?: string): string {
|
||||||
|
return getKeyValue(key)?.toString() ?? defaultString ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function conf(): RuntimeConfig {
|
||||||
|
const dmcaEmail = getKey("DMCA_EMAIL");
|
||||||
|
const chromeExtension = getKey("ONBOARDING_CHROME_EXTENSION_INSTALL_LINK");
|
||||||
|
const firefoxExtension = getKey("ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK");
|
||||||
|
const proxyInstallLink = getKey("ONBOARDING_PROXY_INSTALL_LINK");
|
||||||
|
const turnstileKey = getKey("TURNSTILE_KEY");
|
||||||
|
return {
|
||||||
|
APP_VERSION,
|
||||||
|
GITHUB_LINK,
|
||||||
|
DONATION_LINK,
|
||||||
|
DISCORD_LINK,
|
||||||
|
DMCA_EMAIL: dmcaEmail.length > 0 ? dmcaEmail : null,
|
||||||
|
ONBOARDING_CHROME_EXTENSION_INSTALL_LINK:
|
||||||
|
chromeExtension.length > 0 ? chromeExtension : null,
|
||||||
|
ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK:
|
||||||
|
firefoxExtension.length > 0 ? firefoxExtension : null,
|
||||||
|
ONBOARDING_PROXY_INSTALL_LINK:
|
||||||
|
proxyInstallLink.length > 0 ? proxyInstallLink : null,
|
||||||
|
BACKEND_URL: getKey("BACKEND_URL", BACKEND_URL),
|
||||||
|
TMDB_READ_API_KEY: getKey("TMDB_READ_API_KEY"),
|
||||||
|
PROXY_URLS: getKey("CORS_PROXY_URL")
|
||||||
|
.split(",")
|
||||||
|
.map((v) => v.trim()),
|
||||||
|
NORMAL_ROUTER: getKey("NORMAL_ROUTER", "false") === "true",
|
||||||
|
HAS_ONBOARDING: getKey("HAS_ONBOARDING", "false") === "true",
|
||||||
|
TURNSTILE_KEY: turnstileKey.length > 0 ? turnstileKey : null,
|
||||||
|
DISALLOWED_IDS: getKey("DISALLOWED_IDS", "")
|
||||||
|
.split(",")
|
||||||
|
.map((v) => v.trim())
|
||||||
|
.filter((v) => v.length > 0), // Should be comma-seperated and contain the media type and ID, formatted like so: movie-753342,movie-753342,movie-753342
|
||||||
|
CDN_REPLACEMENTS: getKey("CDN_REPLACEMENTS", "")
|
||||||
|
.split(",")
|
||||||
|
.map((v) =>
|
||||||
|
v
|
||||||
|
.split(":")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter((s) => s.length > 0),
|
||||||
|
)
|
||||||
|
.filter((v) => v.length === 2), // The format is <beforeA>:<afterA>,<beforeB>:<afterB>
|
||||||
|
};
|
||||||
|
}
|
6
src/utils/setup/constants.ts
Normal file
6
src/utils/setup/constants.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export const APP_VERSION = import.meta.env.PACKAGE_VERSION;
|
||||||
|
export const DISCORD_LINK = "https://movie-web.github.io/links/discord";
|
||||||
|
export const GITHUB_LINK = "https://github.com/movie-web/movie-web";
|
||||||
|
export const DONATION_LINK = "https://ko-fi.com/movieweb";
|
||||||
|
export const GA_ID = import.meta.env.VITE_GA_ID;
|
||||||
|
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL;
|
11
src/utils/setup/ga.ts
Normal file
11
src/utils/setup/ga.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
|
import { GA_ID } from "@/setup/constants";
|
||||||
|
|
||||||
|
if (GA_ID) {
|
||||||
|
ReactGA.initialize([
|
||||||
|
{
|
||||||
|
trackingId: GA_ID,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
27
src/utils/setup/i18n.ts
Normal file
27
src/utils/setup/i18n.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import i18n from "i18next";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
|
||||||
|
import { locales } from "@/assets/languages";
|
||||||
|
import { getLocaleInfo } from "@/utils/language";
|
||||||
|
|
||||||
|
// Languages
|
||||||
|
const langCodes = Object.keys(locales);
|
||||||
|
const resources = Object.fromEntries(
|
||||||
|
Object.entries(locales).map((entry) => [entry[0], { translation: entry[1] }]),
|
||||||
|
);
|
||||||
|
i18n.use(initReactI18next).init({
|
||||||
|
fallbackLng: "en",
|
||||||
|
resources,
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false, // not needed for react as it escapes by default
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const appLanguageOptions = langCodes.map((lang) => {
|
||||||
|
const langObj = getLocaleInfo(lang);
|
||||||
|
if (!langObj)
|
||||||
|
throw new Error(`Language with code ${lang} cannot be found in database`);
|
||||||
|
return langObj;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
27
src/utils/setup/pwa.ts
Normal file
27
src/utils/setup/pwa.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { registerSW } from "virtual:pwa-register";
|
||||||
|
|
||||||
|
const intervalMS = 60 * 60 * 1000;
|
||||||
|
|
||||||
|
registerSW({
|
||||||
|
immediate: true,
|
||||||
|
onRegisteredSW(swUrl, r) {
|
||||||
|
if (!r) return;
|
||||||
|
setInterval(async () => {
|
||||||
|
if (!(!r.installing && navigator)) return;
|
||||||
|
|
||||||
|
if ("connection" in navigator && !navigator.onLine) return;
|
||||||
|
|
||||||
|
const resp = await fetch(swUrl, {
|
||||||
|
cache: "no-store",
|
||||||
|
headers: {
|
||||||
|
cache: "no-store",
|
||||||
|
"cache-control": "no-cache",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp?.status === 200) {
|
||||||
|
await r.update();
|
||||||
|
}
|
||||||
|
}, intervalMS);
|
||||||
|
},
|
||||||
|
});
|
Loading…
Reference in a new issue