mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: pipeline templates, analytics opt-out, 83 conversion presets, positioning + e2e modernization
Lands five integrated branches: pipeline templates (#355), analytics opt-out (#354), 83 conversion presets bringing the catalog to 240 tools (#356), self-hosted positioning (#353), and e2e modernization (#351). Integration fixes: aligned stale web analytics tests with the opt-out/allow-list model, closed 3 CodeQL incomplete-sanitization alerts in the i18n generator, resolved settings/index/docs/format-matrix conflicts, and corrected tool counts to 240.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { modalityForExtension } from "@snapotter/shared";
|
||||
import { modalityForExtension, type PipelineTemplate } from "@snapotter/shared";
|
||||
import {
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { TemplatesSection } from "@/components/automate/templates-section";
|
||||
import { BeforeAfterSlider } from "@/components/common/before-after-slider";
|
||||
import { Dropzone } from "@/components/common/dropzone";
|
||||
import { FileLibraryModal } from "@/components/common/file-library-modal";
|
||||
@@ -277,6 +278,13 @@ export function AutomatePage() {
|
||||
[loadSteps],
|
||||
);
|
||||
|
||||
const handleUseTemplate = useCallback(
|
||||
(template: PipelineTemplate) => {
|
||||
loadSteps(template.steps);
|
||||
},
|
||||
[loadSteps],
|
||||
);
|
||||
|
||||
const handleExportPipeline = useCallback((pipeline: SavedPipeline) => {
|
||||
const exportData = {
|
||||
format: "snapotter-pipeline" as const,
|
||||
@@ -739,6 +747,9 @@ export function AutomatePage() {
|
||||
{/* Tool catalog */}
|
||||
<ToolPalette onAddStep={handleAddStep} className="flex-1 min-h-0" />
|
||||
|
||||
{/* Pipeline templates */}
|
||||
<TemplatesSection onUse={handleUseTemplate} emphasized={steps.length === 0} />
|
||||
|
||||
{/* Saved pipelines */}
|
||||
<div className="px-3 py-2 border-t border-border shrink-0">
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
|
||||
@@ -89,7 +89,7 @@ export function HomePage() {
|
||||
if (!search || search.length < 2) return;
|
||||
const timer = setTimeout(() => {
|
||||
import("@/lib/analytics").then(({ track }) => {
|
||||
track(ANALYTICS_EVENTS.SEARCH, { query: search, results_count: searchResults.length });
|
||||
track(ANALYTICS_EVENTS.SEARCH, { results_count: searchResults.length });
|
||||
});
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
@@ -195,6 +195,14 @@ function HomeSearchBar({
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
// Escape steps out of the search field: clear a query if present,
|
||||
// otherwise drop focus back to the page.
|
||||
if (e.key === "Escape") {
|
||||
if (value) onChange("");
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
aria-label={placeholder}
|
||||
className="w-full ps-11 pe-20 py-2.5 rounded-lg border border-border bg-card text-sm text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/40 transition-shadow"
|
||||
@@ -244,7 +252,10 @@ function ModalityTabs({
|
||||
className={cn(
|
||||
"px-3.5 py-1.5 rounded-full text-sm font-medium transition-colors whitespace-nowrap",
|
||||
activeTab === tab.key
|
||||
? "bg-primary text-primary-foreground shadow-sm"
|
||||
? // Fixed dark text on the orange pill (the orange is the same
|
||||
// in both themes). White on Otter Orange is only ~3:1, below
|
||||
// WCAG AA for this 14px label; near-black reaches ~5.8:1.
|
||||
"bg-primary text-[#1a1814] shadow-sm"
|
||||
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -43,9 +43,11 @@ export function PrivacyPolicyPage() {
|
||||
<p>
|
||||
SnapOtter includes basic analytics (tool usage, error reports) to help improve the
|
||||
software. Your files, file names, and personal data are never part of this. Analytics
|
||||
can be disabled by rebuilding with{" "}
|
||||
<code className="text-xs bg-muted px-1 py-0.5 rounded">SNAPOTTER_ANALYTICS=off</code>{" "}
|
||||
-- everything works normally without it.
|
||||
is on by default and can be disabled at runtime. An administrator can turn anonymous
|
||||
product analytics off under Settings {">"} System {">"} Privacy, with no restart or
|
||||
rebuild needed. For a compile-time hard-off, build with{" "}
|
||||
<code className="text-xs bg-muted px-1 py-0.5 rounded">SNAPOTTER_ANALYTICS=off</code>,
|
||||
which strips it from the bundle entirely. Everything works normally without it.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user