mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Merge branch 'fix/biome-lint-debt' into chore/consolidate-v2.0.0
This commit is contained in:
Regular → Executable
@@ -74,7 +74,7 @@ async function getS3(): Promise<S3StorageModule> {
|
|||||||
return s3Mod;
|
return s3Mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useS3(): boolean {
|
function isS3Enabled(): boolean {
|
||||||
return env.STORAGE_MODE === "s3";
|
return env.STORAGE_MODE === "s3";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ let storageReady = false;
|
|||||||
|
|
||||||
export async function ensureStorageDir(): Promise<void> {
|
export async function ensureStorageDir(): Promise<void> {
|
||||||
if (storageReady) return;
|
if (storageReady) return;
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
await s3.checkConnection();
|
await s3.checkConnection();
|
||||||
storageReady = true;
|
storageReady = true;
|
||||||
@@ -115,7 +115,7 @@ export async function ensureStorageDir(): Promise<void> {
|
|||||||
|
|
||||||
export async function saveFile(buffer: Buffer, originalName: string): Promise<string> {
|
export async function saveFile(buffer: Buffer, originalName: string): Promise<string> {
|
||||||
const storedName = generateStoredName(originalName);
|
const storedName = generateStoredName(originalName);
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
await s3.putObject(storedName, buffer);
|
await s3.putObject(storedName, buffer);
|
||||||
return storedName;
|
return storedName;
|
||||||
@@ -138,7 +138,7 @@ export async function saveFile(buffer: Buffer, originalName: string): Promise<st
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function readStoredFile(storedName: string): Promise<Buffer> {
|
export async function readStoredFile(storedName: string): Promise<Buffer> {
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
return s3.getObject(storedName);
|
return s3.getObject(storedName);
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ export async function readStoredFile(storedName: string): Promise<Buffer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function streamStoredFile(storedName: string): Promise<Readable> {
|
export async function streamStoredFile(storedName: string): Promise<Readable> {
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
return s3.getObjectStream(storedName);
|
return s3.getObjectStream(storedName);
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ export async function streamStoredFile(storedName: string): Promise<Readable> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteStoredFile(storedName: string): Promise<void> {
|
export async function deleteStoredFile(storedName: string): Promise<void> {
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
await s3.deleteObject(storedName);
|
await s3.deleteObject(storedName);
|
||||||
return;
|
return;
|
||||||
@@ -177,7 +177,7 @@ let thumbDirReady = false;
|
|||||||
|
|
||||||
async function ensureThumbDir(): Promise<void> {
|
async function ensureThumbDir(): Promise<void> {
|
||||||
if (thumbDirReady) return;
|
if (thumbDirReady) return;
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
thumbDirReady = true;
|
thumbDirReady = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ function thumbPath(storedName: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getCachedThumbnail(storedName: string): Promise<Buffer | null> {
|
export async function getCachedThumbnail(storedName: string): Promise<Buffer | null> {
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
return s3.getThumbnail(storedName);
|
return s3.getThumbnail(storedName);
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ export async function getCachedThumbnail(storedName: string): Promise<Buffer | n
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function saveThumbnail(storedName: string, buffer: Buffer): Promise<void> {
|
export async function saveThumbnail(storedName: string, buffer: Buffer): Promise<void> {
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
await s3.putThumbnail(storedName, buffer);
|
await s3.putThumbnail(storedName, buffer);
|
||||||
return;
|
return;
|
||||||
@@ -219,7 +219,7 @@ export async function saveThumbnail(storedName: string, buffer: Buffer): Promise
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteThumbnail(storedName: string): Promise<void> {
|
export async function deleteThumbnail(storedName: string): Promise<void> {
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
await s3.deleteThumbnail(storedName);
|
await s3.deleteThumbnail(storedName);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function localPath(key: string): string {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useS3(): boolean {
|
function isS3Enabled(): boolean {
|
||||||
return env.STORAGE_MODE === "s3";
|
return env.STORAGE_MODE === "s3";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ export async function assertLocalCapacity(): Promise<void> {
|
|||||||
|
|
||||||
export async function putObject(key: string, data: Buffer): Promise<void> {
|
export async function putObject(key: string, data: Buffer): Promise<void> {
|
||||||
assertValidKey(key);
|
assertValidKey(key);
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
await s3.putGenericObject(key, data);
|
await s3.putGenericObject(key, data);
|
||||||
return;
|
return;
|
||||||
@@ -129,7 +129,7 @@ export async function putObjectStream(
|
|||||||
yield chunk;
|
yield chunk;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
await s3.putGenericObjectStream(key, counter(source));
|
await s3.putGenericObjectStream(key, counter(source));
|
||||||
return written;
|
return written;
|
||||||
@@ -151,7 +151,7 @@ export async function getObjectStream(
|
|||||||
range?: { start: number; end?: number },
|
range?: { start: number; end?: number },
|
||||||
): Promise<Readable> {
|
): Promise<Readable> {
|
||||||
assertValidKey(key);
|
assertValidKey(key);
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
return s3.getGenericObjectStream(key, range);
|
return s3.getGenericObjectStream(key, range);
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ export async function getObjectBuffer(key: string): Promise<Buffer> {
|
|||||||
|
|
||||||
export async function getObjectSize(key: string): Promise<number> {
|
export async function getObjectSize(key: string): Promise<number> {
|
||||||
assertValidKey(key);
|
assertValidKey(key);
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
return s3.getGenericObjectSize(key);
|
return s3.getGenericObjectSize(key);
|
||||||
}
|
}
|
||||||
@@ -184,7 +184,7 @@ export async function objectExists(key: string): Promise<boolean> {
|
|||||||
|
|
||||||
export async function deleteObject(key: string): Promise<void> {
|
export async function deleteObject(key: string): Promise<void> {
|
||||||
assertValidKey(key);
|
assertValidKey(key);
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
await s3.deleteGenericObject(key);
|
await s3.deleteGenericObject(key);
|
||||||
return;
|
return;
|
||||||
@@ -196,7 +196,7 @@ export async function deletePrefix(prefix: string): Promise<void> {
|
|||||||
if (!/^(uploads|outputs)\/[A-Za-z0-9][A-Za-z0-9._-]*\/?$/.test(prefix)) {
|
if (!/^(uploads|outputs)\/[A-Za-z0-9][A-Za-z0-9._-]*\/?$/.test(prefix)) {
|
||||||
throw new Error(`Invalid prefix: ${prefix}`);
|
throw new Error(`Invalid prefix: ${prefix}`);
|
||||||
}
|
}
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
await s3.deleteGenericPrefix(prefix);
|
await s3.deleteGenericPrefix(prefix);
|
||||||
return;
|
return;
|
||||||
@@ -208,7 +208,7 @@ export async function listObjects(prefix: string): Promise<ObjectInfo[]> {
|
|||||||
if (!/^(uploads|outputs)\/[A-Za-z0-9][A-Za-z0-9._-]*\/?$/.test(prefix) || prefix.includes("..")) {
|
if (!/^(uploads|outputs)\/[A-Za-z0-9][A-Za-z0-9._-]*\/?$/.test(prefix) || prefix.includes("..")) {
|
||||||
throw new Error(`Invalid prefix: ${prefix}`);
|
throw new Error(`Invalid prefix: ${prefix}`);
|
||||||
}
|
}
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
return s3.listGenericObjects(prefix);
|
return s3.listGenericObjects(prefix);
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ export async function listObjects(prefix: string): Promise<ObjectInfo[]> {
|
|||||||
// Lists the top-level job directories under a prefix with their mtime so the
|
// Lists the top-level job directories under a prefix with their mtime so the
|
||||||
// TTL sweeper can expire whole jobs. S3 derives them from key listings.
|
// TTL sweeper can expire whole jobs. S3 derives them from key listings.
|
||||||
export async function listJobDirs(prefix: "uploads" | "outputs"): Promise<ObjectInfo[]> {
|
export async function listJobDirs(prefix: "uploads" | "outputs"): Promise<ObjectInfo[]> {
|
||||||
if (useS3()) {
|
if (isS3Enabled()) {
|
||||||
const s3 = await getS3();
|
const s3 = await getS3();
|
||||||
return s3.listGenericJobDirs(prefix);
|
return s3.listGenericJobDirs(prefix);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
|
|
||||||
const audit = auditFromRequest(request);
|
const audit = auditFromRequest(request);
|
||||||
|
|
||||||
if (!user || !user.passwordHash) {
|
if (!user?.passwordHash) {
|
||||||
authAttempts.inc({ method: "password", result: "failure" });
|
authAttempts.inc({ method: "password", result: "failure" });
|
||||||
await audit("LOGIN_FAILED", {
|
await audit("LOGIN_FAILED", {
|
||||||
username: sanitizeAuditInput(body.username),
|
username: sanitizeAuditInput(body.username),
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export async function registerSaml(app: FastifyInstance): Promise<void> {
|
|||||||
return redirectToLogin(reply, "saml_auth_failed");
|
return redirectToLogin(reply, "saml_auth_failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!profile || !profile.nameID) {
|
if (!profile?.nameID) {
|
||||||
request.log.warn("SAML callback: no profile or nameID in assertion");
|
request.log.warn("SAML callback: no profile or nameID in assertion");
|
||||||
authAttempts.inc({ method: "saml", result: "failure" });
|
authAttempts.inc({ method: "saml", result: "failure" });
|
||||||
await audit("SAML_LOGIN_FAILED", { reason: "missing_profile" });
|
await audit("SAML_LOGIN_FAILED", { reason: "missing_profile" });
|
||||||
|
|||||||
@@ -463,7 +463,8 @@ export async function registerBatchRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
// Append results from object storage in original upload order
|
// Append results from object storage in original upload order
|
||||||
try {
|
try {
|
||||||
for (const entry of successEntries) {
|
for (const entry of successEntries) {
|
||||||
const stream = await getObjectStream(entry.outputRef!);
|
if (!entry.outputRef) continue;
|
||||||
|
const stream = await getObjectStream(entry.outputRef);
|
||||||
archive.append(stream, { name: entry.filename });
|
archive.append(stream, { name: entry.filename });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|||||||
import { env } from "../../config.js";
|
import { env } from "../../config.js";
|
||||||
import { db, schema } from "../../db/index.js";
|
import { db, schema } from "../../db/index.js";
|
||||||
import { auditFromRequest } from "../../lib/audit.js";
|
import { auditFromRequest } from "../../lib/audit.js";
|
||||||
import { encrypt, isEncrypted } from "../../lib/encryption.js";
|
import { encrypt } from "../../lib/encryption.js";
|
||||||
import { requirePermission } from "../../permissions.js";
|
import { requirePermission } from "../../permissions.js";
|
||||||
|
|
||||||
const configSchema = z.object({
|
const configSchema = z.object({
|
||||||
|
|||||||
@@ -1161,7 +1161,8 @@ export async function registerPipelineRoutes(app: FastifyInstance): Promise<void
|
|||||||
// Append results from object storage in original upload order
|
// Append results from object storage in original upload order
|
||||||
try {
|
try {
|
||||||
for (const entry of successEntries) {
|
for (const entry of successEntries) {
|
||||||
const stream = await getObjectStream(entry.outputRef!);
|
if (!entry.outputRef) continue;
|
||||||
|
const stream = await getObjectStream(entry.outputRef);
|
||||||
archive.append(stream, { name: entry.filename });
|
archive.append(stream, { name: entry.filename });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ export async function registerProgressRoutes(app: FastifyInstance): Promise<void
|
|||||||
if (!sseListeners.has(jobId)) {
|
if (!sseListeners.has(jobId)) {
|
||||||
sseListeners.set(jobId, new Set());
|
sseListeners.set(jobId, new Set());
|
||||||
}
|
}
|
||||||
sseListeners.get(jobId)!.add(callback);
|
sseListeners.get(jobId)?.add(callback);
|
||||||
|
|
||||||
// Clean up on client disconnect
|
// Clean up on client disconnect
|
||||||
request.raw.on("close", () => {
|
request.raw.on("close", () => {
|
||||||
|
|||||||
@@ -88,9 +88,10 @@ export function registerHtmlToImage(app: FastifyInstance) {
|
|||||||
isMobile: preset.isMobile,
|
isMobile: preset.isMobile,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Zod refine guarantees either url or html is present
|
||||||
const buffer = settings.html
|
const buffer = settings.html
|
||||||
? await captureHtml(settings.html, captureOpts)
|
? await captureHtml(settings.html, captureOpts)
|
||||||
: await capturePage(settings.url!, captureOpts);
|
: await capturePage(settings.url ?? "", captureOpts);
|
||||||
|
|
||||||
const jobId = randomUUID();
|
const jobId = randomUUID();
|
||||||
const ext = settings.format;
|
const ext = settings.format;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function BottomSheet({
|
|||||||
}, [onClose]);
|
}, [onClose]);
|
||||||
|
|
||||||
const bind = useDrag(
|
const bind = useDrag(
|
||||||
({ movement: [, my], last, cancel }) => {
|
({ movement: [, my], last }) => {
|
||||||
// Only allow downward dragging
|
// Only allow downward dragging
|
||||||
if (my < 0) {
|
if (my < 0) {
|
||||||
setTranslateY(0);
|
setTranslateY(0);
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export function Dropzone({
|
|||||||
setUrlError(t.dropzone.urlFetchFailed);
|
setUrlError(t.dropzone.urlFetchFailed);
|
||||||
}
|
}
|
||||||
setUrlLoading(false);
|
setUrlLoading(false);
|
||||||
}, [urlInput, importSingleUrl, onUrlImport, checkFile, acceptDescription]);
|
}, [urlInput, importSingleUrl, onUrlImport, checkFile, acceptDescription, t]);
|
||||||
|
|
||||||
const handleDrag = useCallback((e: DragEvent) => {
|
const handleDrag = useCallback((e: DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function NonNativePreview({
|
|||||||
} finally {
|
} finally {
|
||||||
stopMessageRotation();
|
stopMessageRotation();
|
||||||
}
|
}
|
||||||
}, [file, filename, previewUrl, startMessageRotation, stopMessageRotation]);
|
}, [file, src, filename, previewUrl, startMessageRotation, stopMessageRotation]);
|
||||||
|
|
||||||
const ext = filename.split(".").pop()?.toUpperCase() ?? "";
|
const ext = filename.split(".").pop()?.toUpperCase() ?? "";
|
||||||
const IconComponent = modality === "audio" ? Volume2 : Video;
|
const IconComponent = modality === "audio" ? Volume2 : Video;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export function RouteAnnouncer() {
|
|||||||
const isFirstRender = useRef(true);
|
const isFirstRender = useRef(true);
|
||||||
const announcerRef = useRef<HTMLDivElement>(null);
|
const announcerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: pathname is the intentional trigger for re-announcing on route change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isFirstRender.current) {
|
if (isFirstRender.current) {
|
||||||
isFirstRender.current = false;
|
isFirstRender.current = false;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function CropOptions() {
|
|||||||
if (!cropState) return;
|
if (!cropState) return;
|
||||||
|
|
||||||
const preset = ASPECT_RATIOS.find((p) => p.label === label);
|
const preset = ASPECT_RATIOS.find((p) => p.label === label);
|
||||||
if (!preset || !preset.value) {
|
if (!preset?.value) {
|
||||||
setCropState({ ...cropState, aspectRatio: null });
|
setCropState({ ...cropState, aspectRatio: null });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export function useCropTool(): CropToolApi {
|
|||||||
(label: string) => {
|
(label: string) => {
|
||||||
setAspectRatioState(label);
|
setAspectRatioState(label);
|
||||||
const preset = ASPECT_RATIOS.find((p) => p.label === label);
|
const preset = ASPECT_RATIOS.find((p) => p.label === label);
|
||||||
if (!preset || !preset.value || !cropState) return;
|
if (!preset?.value || !cropState) return;
|
||||||
const ratio = preset.value;
|
const ratio = preset.value;
|
||||||
|
|
||||||
let w = cropState.width;
|
let w = cropState.width;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Download, Search, Trash2 } from "lucide-react";
|
import { Download, Search, Trash2 } from "lucide-react";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { useTranslation } from "@/contexts/i18n-context";
|
import { useTranslation } from "@/contexts/i18n-context";
|
||||||
import { getFileDownloadUrl } from "@/lib/api";
|
import { getFileDownloadUrl } from "@/lib/api";
|
||||||
import { format } from "@/lib/format";
|
import { format } from "@/lib/format";
|
||||||
@@ -22,7 +21,6 @@ export function FileList({ filterMimePrefix }: { filterMimePrefix?: string }) {
|
|||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
} = useFilesPageStore();
|
} = useFilesPageStore();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const listRef = useRef<HTMLDivElement>(null);
|
const listRef = useRef<HTMLDivElement>(null);
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { Check, Copy, Download, Search } from "lucide-react";
|
import { Check, Copy, Download, Search } from "lucide-react";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { ProgressCard } from "@/components/common/progress-card";
|
import { ProgressCard } from "@/components/common/progress-card";
|
||||||
import { useTranslation } from "@/contexts/i18n-context";
|
|
||||||
import { formatHeaders } from "@/lib/api";
|
import { formatHeaders } from "@/lib/api";
|
||||||
import { format } from "@/lib/format";
|
|
||||||
import { copyToClipboard } from "@/lib/utils";
|
import { copyToClipboard } from "@/lib/utils";
|
||||||
import { useFileStore } from "@/stores/file-store";
|
import { useFileStore } from "@/stores/file-store";
|
||||||
|
|
||||||
@@ -108,7 +106,6 @@ function scanOneFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function BarcodeReadSettings() {
|
export function BarcodeReadSettings() {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { files, processing, error, setProcessing, setError } = useFileStore();
|
const { files, processing, error, setProcessing, setError } = useFileStore();
|
||||||
|
|
||||||
const [tryHarder, setTryHarder] = useState(false);
|
const [tryHarder, setTryHarder] = useState(false);
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ import { Download } from "lucide-react";
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { ProgressCard } from "@/components/common/progress-card";
|
import { ProgressCard } from "@/components/common/progress-card";
|
||||||
import { useTranslation } from "@/contexts/i18n-context";
|
|
||||||
import { useToolProcessor } from "@/hooks/use-tool-processor";
|
import { useToolProcessor } from "@/hooks/use-tool-processor";
|
||||||
import { format } from "@/lib/format";
|
|
||||||
import { useFileStore } from "@/stores/file-store";
|
import { useFileStore } from "@/stores/file-store";
|
||||||
|
|
||||||
// ── Presets ──────────────────────────────────────────────────────────
|
// ── Presets ──────────────────────────────────────────────────────────
|
||||||
@@ -227,7 +225,6 @@ export function BorderControls({
|
|||||||
onChange,
|
onChange,
|
||||||
onImageStyle,
|
onImageStyle,
|
||||||
}: BorderControlsProps) {
|
}: BorderControlsProps) {
|
||||||
const { t } = useTranslation();
|
|
||||||
const [selectedPreset, setSelectedPreset] = useState<string | null>(null);
|
const [selectedPreset, setSelectedPreset] = useState<string | null>(null);
|
||||||
const [borderWidth, setBorderWidth] = useState(10);
|
const [borderWidth, setBorderWidth] = useState(10);
|
||||||
const [borderColor, setBorderColor] = useState("#000000");
|
const [borderColor, setBorderColor] = useState("#000000");
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ function displayUrl(img: CollageImage): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CollagePreview() {
|
export function CollagePreview() {
|
||||||
const { t } = useTranslation();
|
|
||||||
const images = useCollageStore((s) => s.images);
|
const images = useCollageStore((s) => s.images);
|
||||||
const templateId = useCollageStore((s) => s.templateId);
|
const templateId = useCollageStore((s) => s.templateId);
|
||||||
const phase = useCollageStore((s) => s.phase);
|
const phase = useCollageStore((s) => s.phase);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Download, Loader2 } from "lucide-react";
|
import { Download, Loader2 } from "lucide-react";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { CollapsibleSection } from "@/components/common/collapsible-section";
|
import { CollapsibleSection } from "@/components/common/collapsible-section";
|
||||||
import { useTranslation } from "@/contexts/i18n-context";
|
|
||||||
import { formatHeaders } from "@/lib/api";
|
import { formatHeaders } from "@/lib/api";
|
||||||
import {
|
import {
|
||||||
COLLAGE_TEMPLATES,
|
COLLAGE_TEMPLATES,
|
||||||
@@ -38,7 +37,6 @@ const BG_PRESETS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function CollageSettings() {
|
export function CollageSettings() {
|
||||||
const { t } = useTranslation();
|
|
||||||
const store = useCollageStore();
|
const store = useCollageStore();
|
||||||
const {
|
const {
|
||||||
images,
|
images,
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { Download, Loader2, Upload } from "lucide-react";
|
import { Download, Loader2, Upload } from "lucide-react";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useTranslation } from "@/contexts/i18n-context";
|
|
||||||
import { formatHeaders } from "@/lib/api";
|
import { formatHeaders } from "@/lib/api";
|
||||||
import { useFileStore } from "@/stores/file-store";
|
import { useFileStore } from "@/stores/file-store";
|
||||||
export function CompareSettings() {
|
export function CompareSettings() {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { files, processing, error, setProcessing, setError, setProcessedUrl } = useFileStore();
|
const { files, processing, error, setProcessing, setError, setProcessedUrl } = useFileStore();
|
||||||
const [secondFile, setSecondFile] = useState<File | null>(null);
|
const [secondFile, setSecondFile] = useState<File | null>(null);
|
||||||
const [similarity, setSimilarity] = useState<number | null>(null);
|
const [similarity, setSimilarity] = useState<number | null>(null);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const useFeaturesStore = create<FeaturesState>((set, get) => {
|
|||||||
try {
|
try {
|
||||||
await refreshBundles();
|
await refreshBundles();
|
||||||
const updated = get().bundles.find((b) => b.id === bundleId);
|
const updated = get().bundles.find((b) => b.id === bundleId);
|
||||||
if (!updated || updated.status !== "installing") {
|
if (updated?.status !== "installing") {
|
||||||
clearInterval(pollRefs[bundleId]);
|
clearInterval(pollRefs[bundleId]);
|
||||||
delete pollRefs[bundleId];
|
delete pollRefs[bundleId];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user