mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: modality-based URLs (/image/resize, /video/compress-video)
Tool routes now include the modality prefix: /image/crop, /video/trim-video, /audio/convert-audio, /document/merge-pdf, /data/csv-json. Old URLs (e.g. /resize) redirect to the new modality-prefixed path automatically. Routes are computed by prepending MODALITY_URL_SLUG to each tool's route in the TOOLS array at module init. The App.tsx route changed from /:toolId to /:modality/:toolId, with a LegacyToolRedirect fallback.
This commit is contained in:
+23
-7
@@ -1,6 +1,6 @@
|
|||||||
import { APP_VERSION, en, shouldShowConsent } from "@snapotter/shared";
|
import { APP_VERSION, TOOLS, en, shouldShowConsent } from "@snapotter/shared";
|
||||||
import { Component, type ErrorInfo, lazy, type ReactNode, Suspense, useEffect } from "react";
|
import { Component, type ErrorInfo, lazy, type ReactNode, Suspense, useEffect } from "react";
|
||||||
import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom";
|
import { BrowserRouter, Navigate, Route, Routes, useLocation, useParams } from "react-router-dom";
|
||||||
import { Toaster, toast } from "sonner";
|
import { Toaster, toast } from "sonner";
|
||||||
import { ConnectionMonitor } from "./components/common/connection-monitor";
|
import { ConnectionMonitor } from "./components/common/connection-monitor";
|
||||||
import { KeyboardShortcutProvider } from "./components/common/keyboard-shortcut-provider";
|
import { KeyboardShortcutProvider } from "./components/common/keyboard-shortcut-provider";
|
||||||
@@ -77,6 +77,13 @@ class ErrorBoundary extends Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LegacyToolRedirect() {
|
||||||
|
const { toolId } = useParams<{ toolId: string }>();
|
||||||
|
const tool = TOOLS.find((t) => t.id === toolId);
|
||||||
|
if (tool) return <Navigate to={tool.route} replace />;
|
||||||
|
return <Navigate to="/" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
function AuthGuard({ children }: { children: React.ReactNode }) {
|
function AuthGuard({ children }: { children: React.ReactNode }) {
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
@@ -239,17 +246,26 @@ export function App() {
|
|||||||
{/* Redirects: old color tools consolidated into adjust-colors */}
|
{/* Redirects: old color tools consolidated into adjust-colors */}
|
||||||
<Route
|
<Route
|
||||||
path="/brightness-contrast"
|
path="/brightness-contrast"
|
||||||
element={<Navigate to="/adjust-colors" replace />}
|
element={<Navigate to="/image/adjust-colors" replace />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/saturation"
|
||||||
|
element={<Navigate to="/image/adjust-colors" replace />}
|
||||||
/>
|
/>
|
||||||
<Route path="/saturation" element={<Navigate to="/adjust-colors" replace />} />
|
|
||||||
<Route
|
<Route
|
||||||
path="/color-channels"
|
path="/color-channels"
|
||||||
element={<Navigate to="/adjust-colors" replace />}
|
element={<Navigate to="/image/adjust-colors" replace />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/color-effects"
|
||||||
|
element={<Navigate to="/image/adjust-colors" replace />}
|
||||||
/>
|
/>
|
||||||
<Route path="/color-effects" element={<Navigate to="/adjust-colors" replace />} />
|
|
||||||
<Route path="/analytics-consent" element={<AnalyticsConsentPage />} />
|
<Route path="/analytics-consent" element={<AnalyticsConsentPage />} />
|
||||||
<Route path="/editor" element={<EditorPage />} />
|
<Route path="/editor" element={<EditorPage />} />
|
||||||
<Route path="/:toolId" element={<ToolPage />} />
|
{/* Modality-prefixed tool routes */}
|
||||||
|
<Route path="/:modality/:toolId" element={<ToolPage />} />
|
||||||
|
{/* Legacy: redirect old /:toolId URLs to /:modality/:toolId */}
|
||||||
|
<Route path="/:toolId" element={<LegacyToolRedirect />} />
|
||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AUDIO_INPUTS, IMAGE_INPUTS, SUBTITLE_INPUTS, VIDEO_INPUTS } from "./modality.js";
|
import { AUDIO_INPUTS, IMAGE_INPUTS, MODALITY_URL_SLUG, SUBTITLE_INPUTS, VIDEO_INPUTS } from "./modality.js";
|
||||||
import type { CategoryInfo, SocialMediaPreset, Tool } from "./types.js";
|
import type { CategoryInfo, SocialMediaPreset, Tool } from "./types.js";
|
||||||
|
|
||||||
export const CATEGORIES: CategoryInfo[] = [
|
export const CATEGORIES: CategoryInfo[] = [
|
||||||
@@ -1783,6 +1783,10 @@ export const TOOLS: Tool[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
for (const tool of TOOLS) {
|
||||||
|
tool.route = `/${MODALITY_URL_SLUG[tool.modality]}${tool.route}`;
|
||||||
|
}
|
||||||
|
|
||||||
export const SOCIAL_MEDIA_PRESETS: SocialMediaPreset[] = [
|
export const SOCIAL_MEDIA_PRESETS: SocialMediaPreset[] = [
|
||||||
{ platform: "Instagram", name: "Post (Square)", width: 1080, height: 1080 },
|
{ platform: "Instagram", name: "Post (Square)", width: 1080, height: 1080 },
|
||||||
{ platform: "Instagram", name: "Story / Reel", width: 1080, height: 1920 },
|
{ platform: "Instagram", name: "Story / Reel", width: 1080, height: 1920 },
|
||||||
|
|||||||
@@ -119,6 +119,14 @@ export const DOCUMENT_INPUTS = [
|
|||||||
];
|
];
|
||||||
export const FILE_INPUTS = [".csv", ".json", ".xml", ".yaml", ".yml", ".zip"];
|
export const FILE_INPUTS = [".csv", ".json", ".xml", ".yaml", ".yml", ".zip"];
|
||||||
|
|
||||||
|
export const MODALITY_URL_SLUG: Record<Modality, string> = {
|
||||||
|
image: "image",
|
||||||
|
video: "video",
|
||||||
|
audio: "audio",
|
||||||
|
document: "document",
|
||||||
|
file: "data",
|
||||||
|
};
|
||||||
|
|
||||||
export function detectModalityFromMime(mime: string): Modality {
|
export function detectModalityFromMime(mime: string): Modality {
|
||||||
if (mime.startsWith("image/")) return "image";
|
if (mime.startsWith("image/")) return "image";
|
||||||
if (mime.startsWith("video/")) return "video";
|
if (mime.startsWith("video/")) return "video";
|
||||||
|
|||||||
Reference in New Issue
Block a user