feat: enhance canvas functionality with scissors mode and node template updates
- Implemented visual feedback and cursor changes for scissors mode in dark and light themes, improving user interaction during edge manipulation. - Updated node template picker to include new keywords for AI image generation, enhancing searchability. - Renamed and categorized node types for clarity, including updates to asset and prompt nodes. - Added support for video nodes and adjusted related components for improved media handling on the canvas.
This commit is contained in:
79
app/api/pexels-video/route.ts
Normal file
79
app/api/pexels-video/route.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const ALLOWED_HOSTS = new Set([
|
||||
"videos.pexels.com",
|
||||
"player.vimeo.com",
|
||||
"vod-progressive.pexels.com",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Proxies Pexels/Vimeo MP4 streams so playback works when the browser’s
|
||||
* Referer (e.g. localhost) would be rejected by the CDN.
|
||||
* Forwards Range for seeking; whitelists known video hosts from the Pexels API.
|
||||
*/
|
||||
export async function GET(request: NextRequest): Promise<NextResponse> {
|
||||
const raw = request.nextUrl.searchParams.get("u");
|
||||
if (!raw) {
|
||||
return new NextResponse("Missing u", { status: 400 });
|
||||
}
|
||||
|
||||
let target: URL;
|
||||
try {
|
||||
target = new URL(raw);
|
||||
} catch {
|
||||
return new NextResponse("Invalid URL", { status: 400 });
|
||||
}
|
||||
|
||||
if (target.protocol !== "https:") {
|
||||
return new NextResponse("HTTPS only", { status: 400 });
|
||||
}
|
||||
|
||||
if (!ALLOWED_HOSTS.has(target.hostname)) {
|
||||
return new NextResponse("Host not allowed", { status: 403 });
|
||||
}
|
||||
|
||||
const upstreamHeaders: HeadersInit = {
|
||||
Referer: "https://www.pexels.com/",
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0",
|
||||
};
|
||||
const range = request.headers.get("range");
|
||||
if (range) upstreamHeaders.Range = range;
|
||||
const ifRange = request.headers.get("if-range");
|
||||
if (ifRange) upstreamHeaders["If-Range"] = ifRange;
|
||||
|
||||
let upstream: Response;
|
||||
try {
|
||||
upstream = await fetch(target.toString(), {
|
||||
headers: upstreamHeaders,
|
||||
redirect: "follow",
|
||||
});
|
||||
} catch {
|
||||
return new NextResponse("Upstream fetch failed", { status: 502 });
|
||||
}
|
||||
|
||||
const out = new Headers();
|
||||
const copy = [
|
||||
"content-type",
|
||||
"content-length",
|
||||
"accept-ranges",
|
||||
"content-range",
|
||||
"etag",
|
||||
"last-modified",
|
||||
"cache-control",
|
||||
] as const;
|
||||
for (const name of copy) {
|
||||
const v = upstream.headers.get(name);
|
||||
if (v) out.set(name, v);
|
||||
}
|
||||
|
||||
if (!upstream.body) {
|
||||
return new NextResponse(null, { status: upstream.status, headers: out });
|
||||
}
|
||||
|
||||
return new NextResponse(upstream.body, {
|
||||
status: upstream.status,
|
||||
headers: out,
|
||||
});
|
||||
}
|
||||
@@ -203,12 +203,54 @@
|
||||
stroke: rgba(189, 195, 199, 0.35);
|
||||
}
|
||||
|
||||
/* Scherenmodus: Scheren-Cursor (Teal, Fallback crosshair) */
|
||||
.react-flow.canvas-scissors-mode .react-flow__pane,
|
||||
.react-flow.canvas-scissors-mode .react-flow__edge-interaction {
|
||||
cursor:
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%230d9488' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='6' cy='6' r='3'/%3E%3Ccircle cx='6' cy='18' r='3'/%3E%3Cpath d='M20 4 8.12 15.88M14.47 14.48 20 20M8.12 8.12 12 12'/%3E%3C/svg%3E")
|
||||
12 12,
|
||||
crosshair;
|
||||
/*
|
||||
* Scherenmodus: Lucide „scissors-line-dashed“, 90° gegen Uhrzeigersinn (in SVG).
|
||||
* Dunkles Flow: helle Outline + heller Glow · helles Flow: dunkle Outline + dunkler Glow.
|
||||
*/
|
||||
.react-flow.dark.canvas-scissors-mode .react-flow__pane,
|
||||
.react-flow.dark.canvas-scissors-mode .react-flow__edge-interaction {
|
||||
cursor: url("/cursors/scissors-cursor-dark-canvas.svg") 12 12, crosshair;
|
||||
}
|
||||
|
||||
.react-flow:not(.dark).canvas-scissors-mode .react-flow__pane,
|
||||
.react-flow:not(.dark).canvas-scissors-mode .react-flow__edge-interaction {
|
||||
cursor: url("/cursors/scissors-cursor-light-canvas.svg") 12 12, crosshair;
|
||||
}
|
||||
|
||||
/* Scherenmodus: Hover auf Verbindung = Aufleuchten (Farben wie Scheren-Cursor) */
|
||||
.react-flow.dark.canvas-scissors-mode .react-flow__edge:not(.temp) .react-flow__edge-path {
|
||||
transition:
|
||||
stroke 0.12s ease,
|
||||
filter 0.12s ease;
|
||||
}
|
||||
|
||||
.react-flow.dark.canvas-scissors-mode
|
||||
.react-flow__edge:not(.temp):hover
|
||||
.react-flow__edge-path {
|
||||
stroke: #ffffff !important;
|
||||
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.9))
|
||||
drop-shadow(0 0 10px rgba(255, 255, 255, 0.5));
|
||||
}
|
||||
|
||||
.react-flow:not(.dark).canvas-scissors-mode
|
||||
.react-flow__edge:not(.temp)
|
||||
.react-flow__edge-path {
|
||||
transition:
|
||||
stroke 0.12s ease,
|
||||
filter 0.12s ease;
|
||||
}
|
||||
|
||||
.react-flow:not(.dark).canvas-scissors-mode
|
||||
.react-flow__edge:not(.temp):hover
|
||||
.react-flow__edge-path {
|
||||
stroke: #27272a !important;
|
||||
filter: drop-shadow(0 0 2px rgba(39, 39, 42, 0.75))
|
||||
drop-shadow(0 0 9px rgba(39, 39, 42, 0.4));
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.react-flow.canvas-scissors-mode .react-flow__edge:not(.temp) .react-flow__edge-path {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user