mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: major editor overhaul (assets, properties, timeline, fonts) (#709)
* feat: major editor overhaul (assets, properties, timeline, fonts) Refactor editor core systems to standardize UI architecture and improve performance. Assets & Properties: - Replace monolithic property items with composable `Section` architecture. - Add specialized sections for Transform, Blending, and Text. - Implement `NumberField` with scrubbing and math evaluation. - Add new ColorPicker with EyeDropper and multiple format support. - Standardize asset panels using new `PanelView` layout. Fonts & Stickers: - Implement custom font atlas/sprite system for high-performance previews. - Add virtualized FontPicker with search and favorites. - Refactor stickers to use a provider-based architecture (icons, emoji, flags, shapes). - Standardize sticker IDs to `provider:value` format. Timeline & Interaction: - Convert bookmarks to rich objects with notes, colors, and duration. - Refactor drag-and-drop to use Command pattern (enabling proper undo/redo). - Add Shift modifier to disable snapping during moves/resizes. - Add new overlays for layout guides and text editing. Renderer: - Add support for multi-line text, custom line-height, and letter-spacing. - Implement global composite operation (blend modes). - Update sticker node to resolve dynamic provider IDs. Infrastructure: - Add storage migrations (v3->v6) for text weights, sticker IDs, and bookmarks. - Update global styles and core UI components (Button, Input, Popover). * add ts-nocheck directive to settings-legacy.tsx to suppress TypeScript errors * fix: correct global composite operation assignment in TextNode to ensure proper blend mode handling * deleted shadcn components with errors * formatting * fix linter issues * migrate from next middleware to proxy * add missing component back * add breadcrumb back * chore: add @radix-ui/react-primitive deps * chore: more deps * chore: add missing env vars to bun-ci * next env
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import type { FontAtlas } from "@/types/fonts";
|
||||
import { SYSTEM_FONTS } from "@/constants/font-constants";
|
||||
|
||||
const GOOGLE_FONTS_CSS = "https://fonts.googleapis.com/css2";
|
||||
|
||||
const fullLoaded = new Set<string>();
|
||||
|
||||
let cachedAtlas: FontAtlas | null = null;
|
||||
let atlasFetchPromise: Promise<FontAtlas | null> | null = null;
|
||||
|
||||
function encodeFamily(family: string): string {
|
||||
return family.replace(/ /g, "+");
|
||||
}
|
||||
|
||||
export function getCachedFontAtlas(): FontAtlas | null {
|
||||
return cachedAtlas;
|
||||
}
|
||||
|
||||
export function clearFontAtlasCache(): void {
|
||||
cachedAtlas = null;
|
||||
atlasFetchPromise = null;
|
||||
}
|
||||
|
||||
async function fetchAtlas(): Promise<FontAtlas | null> {
|
||||
if (cachedAtlas) return cachedAtlas;
|
||||
if (atlasFetchPromise) return atlasFetchPromise;
|
||||
|
||||
atlasFetchPromise = fetch("/fonts/font-atlas.json")
|
||||
.then(async (response) => {
|
||||
if (!response.ok) return null;
|
||||
const data: FontAtlas = await response.json();
|
||||
cachedAtlas = data;
|
||||
return data;
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
return atlasFetchPromise;
|
||||
}
|
||||
|
||||
function preloadChunkImages({ atlas }: { atlas: FontAtlas }): void {
|
||||
const maxChunk = Math.max(
|
||||
...Object.values(atlas.fonts).map((entry) => entry.ch),
|
||||
);
|
||||
for (let i = 0; i <= maxChunk; i++) {
|
||||
const img = new Image();
|
||||
img.src = `/fonts/font-chunk-${i}.avif`;
|
||||
}
|
||||
}
|
||||
|
||||
export function prefetchFontAtlas(): Promise<FontAtlas | null> {
|
||||
return fetchAtlas().then((atlas) => {
|
||||
if (atlas) preloadChunkImages({ atlas });
|
||||
return atlas;
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadFullFont({
|
||||
family,
|
||||
weights = [400, 700],
|
||||
}: {
|
||||
family: string;
|
||||
weights?: number[];
|
||||
}): Promise<void> {
|
||||
if (fullLoaded.has(family)) return;
|
||||
|
||||
const url = `${GOOGLE_FONTS_CSS}?family=${encodeFamily(family)}:wght@${weights.join(";")}&display=swap`;
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = url;
|
||||
document.head.appendChild(link);
|
||||
await new Promise<void>((resolve) => {
|
||||
link.addEventListener("load", () => resolve(), { once: true });
|
||||
link.addEventListener("error", () => resolve(), { once: true });
|
||||
});
|
||||
await Promise.all(
|
||||
weights.map((weight) =>
|
||||
document.fonts.load(`${weight} 16px "${family.replace(/"/g, '\\"')}"`),
|
||||
),
|
||||
);
|
||||
fullLoaded.add(family);
|
||||
}
|
||||
|
||||
export async function loadFonts({
|
||||
families,
|
||||
}: {
|
||||
families: string[];
|
||||
}): Promise<void> {
|
||||
const googleFonts = families.filter((family) => !SYSTEM_FONTS.has(family));
|
||||
await Promise.all(googleFonts.map((family) => loadFullFont({ family })));
|
||||
}
|
||||
Reference in New Issue
Block a user