diff --git a/.cursor/rules/ultracite.mdc b/.cursor/rules/ultracite.mdc
index c8208f06..78aa4a29 100644
--- a/.cursor/rules/ultracite.mdc
+++ b/.cursor/rules/ultracite.mdc
@@ -6,7 +6,7 @@ alwaysApply: true
# Project Context
-Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.
+Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Prettier's formatter.
## Key Principles
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index b6d67872..2661e182 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the overall
+- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
-* The use of sexualized language or imagery, and sexual attention or advances of
+- The use of sexualized language or imagery, and sexual attention or advances of
any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email address,
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email address,
without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
+- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index c9684d1f..01965d41 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -1,6 +1,6 @@
name: Bug report
description: Create a report to help us improve
-title: '[BUG] '
+title: "[BUG] "
labels: bug
body:
- type: input
@@ -15,7 +15,7 @@ body:
id: Browser
attributes:
label: Browser
- description: Please enter the browser on which you encountered the bug.
+ description: Please enter the browser on which you encountered the bug.
placeholder: e.g. Chrome 137, Firefox 137, Safari 17
validations:
required: true
@@ -67,4 +67,4 @@ body:
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
- required: false
\ No newline at end of file
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 49a00eb6..336e2d6a 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -1,6 +1,6 @@
name: Feature request
description: Suggest an idea for OpenCut
-title: '[FEATURE] '
+title: "[FEATURE] "
labels: enhancement
body:
- type: markdown
@@ -39,4 +39,4 @@ body:
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
- required: false
\ No newline at end of file
+ required: false
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
index f6d94c5e..1369bbab 100644
--- a/.github/SECURITY.md
+++ b/.github/SECURITY.md
@@ -25,4 +25,4 @@ Please do not report security vulnerabilities through public GitHub issues.
- We will provide a detailed response within 5 business days
- We will keep you updated on our progress
-Thank you for helping keep OpenCut secure!
\ No newline at end of file
+Thank you for helping keep OpenCut secure!
diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md
index 892a0f7a..1df3d7bd 100644
--- a/.github/SUPPORT.md
+++ b/.github/SUPPORT.md
@@ -3,21 +3,25 @@
Thanks for using OpenCut! If you need help, here are your options:
## Documentation
+
- Check our [README](../README.md) for basic setup instructions
- Review the [Contributing Guidelines](CONTRIBUTING.md) for development setup
## Issues
+
- **Bug reports**: Use the bug report template
- **Feature requests**: Use the feature request template
- **Questions**: Use GitHub Discussions for general questions
## Community
+
- Join our discussions on GitHub
- Follow the [Code of Conduct](CODE_OF_CONDUCT.md)
## Response Times
+
- Issues are typically triaged within 2-3 business days
- Feature requests may take longer to evaluate
- Security issues are handled with priority
-We appreciate your patience and contributions to making OpenCut better!
\ No newline at end of file
+We appreciate your patience and contributions to making OpenCut better!
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 5d6e71f2..77e361d1 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -3,15 +3,18 @@ applyTo: "**/*.{ts,tsx,js,jsx}"
---
# Project Context
+
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.
## Key Principles
+
- Zero configuration required
- Subsecond performance
- Maximum type safety
- AI-friendly code generation
## Before Writing Code
+
1. Analyze existing patterns in the codebase
2. Consider edge cases and error scenarios
3. Follow the rules below strictly
@@ -20,6 +23,7 @@ Ultracite enforces strict type safety, accessibility standards, and consistent c
## Rules
### Accessibility (a11y)
+
- Don't use `accessKey` attribute on any HTML element.
- Don't set `aria-hidden="true"` on focusable elements.
- Don't add ARIA roles, states, and properties to elements that don't support them.
@@ -56,6 +60,7 @@ Ultracite enforces strict type safety, accessibility standards, and consistent c
- Use correct ISO language/country codes for the `lang` attribute.
### Code Complexity and Quality
+
- Don't use consecutive spaces in regular expression literals.
- Don't use the `arguments` object.
- Don't use primitive type aliases or misleading types.
@@ -111,6 +116,7 @@ Ultracite enforces strict type safety, accessibility standards, and consistent c
- Don't use literal numbers that lose precision.
### React and JSX Best Practices
+
- Don't use the return value of React.render.
- Make sure all dependencies are correctly specified in React hooks.
- Make sure all React hooks are called from the top level of component functions.
@@ -129,6 +135,7 @@ Ultracite enforces strict type safety, accessibility standards, and consistent c
- Watch out for possible "wrong" semicolons inside JSX elements.
### Correctness and Safety
+
- Don't assign a value to itself.
- Don't return a value from a setter.
- Don't compare expressions that modify string case with non-compliant values.
@@ -153,7 +160,7 @@ Ultracite enforces strict type safety, accessibility standards, and consistent c
- Don't use bitwise operators.
- Don't use expressions where the operation doesn't change the value.
- Make sure Promise-like statements are handled appropriately.
-- Don't use __dirname and __filename in the global scope.
+- Don't use **dirname and **filename in the global scope.
- Prevent import cycles.
- Don't use configured elements.
- Don't hardcode sensitive data like API keys and tokens.
@@ -184,6 +191,7 @@ Ultracite enforces strict type safety, accessibility standards, and consistent c
- Don't use `target="_blank"` without `rel="noopener"`.
### TypeScript Best Practices
+
- Don't use TypeScript enums.
- Don't export imported variables.
- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
@@ -208,6 +216,7 @@ Ultracite enforces strict type safety, accessibility standards, and consistent c
- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
### Style and Consistency
+
- Don't use global `eval()`.
- Don't use callbacks in asynchronous tests and hooks.
- Don't use negation in `if` statements that have `else` clauses.
@@ -295,30 +304,34 @@ Ultracite enforces strict type safety, accessibility standards, and consistent c
- Make sure to use the "use strict" directive in script files.
### Next.js Specific Rules
+
- Don't use ` ` elements in Next.js projects.
- Don't use `
` elements in Next.js projects.
-- Don't import next/document outside of pages/_document.jsx in Next.js projects.
-- Don't use the next/head module in pages/_document.js on Next.js projects.
+- Don't import next/document outside of pages/\_document.jsx in Next.js projects.
+- Don't use the next/head module in pages/\_document.js on Next.js projects.
### Testing Best Practices
+
- Don't use export or module.exports in test files.
- Don't use focused tests.
- Make sure the assertion function, like expect, is placed inside an it() function call.
- Don't use disabled tests.
## Common Tasks
+
- `npx ultracite init` - Initialize Ultracite in your project
- `npx ultracite format` - Format and fix code automatically
- `npx ultracite lint` - Check for issues without fixing
## Example: Error Handling
+
```typescript
// β
Good: Comprehensive error handling
try {
const result = await fetchData();
return { success: true, data: result };
} catch (error) {
- console.error('API call failed:', error);
+ console.error("API call failed:", error);
return { success: false, error: error.message };
}
@@ -328,4 +341,4 @@ try {
} catch (e) {
console.log(e);
}
-```
\ No newline at end of file
+```
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index b7686c4b..b9664c76 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -24,9 +24,10 @@ Please describe the tests that you ran to verify your changes. Provide instructi
- [ ] Test B
**Test Configuration**:
-* Node version:
-* Browser (if applicable):
-* Operating System:
+
+- Node version:
+- Browser (if applicable):
+- Operating System:
## Screenshots (if applicable)
@@ -46,4 +47,4 @@ Add screenshots to help explain your changes.
## Additional context
-Add any other context about the pull request here.
+Add any other context about the pull request here.
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..4628520e
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+.git
+apps/web/.next/
+apps/web/public/
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
index 8b0bc4ef..b4bfed35 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,3 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"]
-}
\ No newline at end of file
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8eff0c83..dbab3048 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,17 +1,16 @@
{
- "editor.defaultFormatter": "esbenp.prettier-vscode",
- "[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- },
- "typescript.tsdk": "node_modules/typescript/lib",
- "editor.formatOnSave": true,
- "editor.formatOnPaste": true,
- "emmet.showExpandedAbbreviation": "never",
- "[typescriptreact]": {
- "editor.defaultFormatter": "vscode.typescript-language-features"
- },
- "[typescript]": {
- "editor.defaultFormatter": "vscode.typescript-language-features"
- }
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "editor.formatOnSave": true,
+ "editor.formatOnPaste": true,
+ "emmet.showExpandedAbbreviation": "never",
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "vscode.typescript-language-features"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
}
-
\ No newline at end of file
+}
diff --git a/AGENTS.md b/AGENTS.md
index ded7f32a..7cbe7736 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -32,16 +32,17 @@ import { useEditor } from '@/hooks/use-editor';
function MyComponent() {
const editor = useEditor();
const tracks = editor.timeline.getTracks();
-
+
// Call methods
editor.timeline.addTrack({ type: 'media' });
-
+
// Display data (auto re-renders on changes)
return {tracks.length} tracks
;
}
```
The hook:
+
- Returns the singleton instance
- Subscribes to all manager changes
- Automatically re-renders when state changes
@@ -52,10 +53,10 @@ The hook:
```typescript
// In utilities, event handlers, or non-React code
-import { EditorCore } from '@/core';
+import { EditorCore } from "@/core";
const editor = EditorCore.getInstance();
-await editor.export({ format: 'mp4', quality: 'high' });
+await editor.export({ format: "mp4", quality: "high" });
```
## Actions System
@@ -65,6 +66,7 @@ Actions are the trigger layer for user-initiated operations. The single source o
**To add a new action:**
1. Add it to `ACTIONS` in `@/lib/actions/definitions.ts`:
+
```typescript
export const ACTIONS = {
"my-action": {
@@ -73,14 +75,19 @@ export const ACTIONS = {
defaultShortcuts: ["ctrl+m"],
},
// ...
-}
+};
```
2. Add handler in `@/hooks/use-editor-actions.ts`:
+
```typescript
-useActionHandler("my-action", () => {
- // implementation
-}, undefined);
+useActionHandler(
+ "my-action",
+ () => {
+ // implementation
+ },
+ undefined,
+);
```
**In components, use `invokeAction()` for user-triggered operations:**
@@ -102,6 +109,7 @@ Direct `editor.xxx()` calls are for internal use (commands, tests, complex multi
Commands handle undo/redo. They live in `@/lib/commands/` organized by domain (timeline, media, scene).
Each command extends `Command` from `@/lib/commands/base-command` and implements:
+
- `execute()` - saves current state, then does the mutation
- `undo()` - restores the saved state
diff --git a/TODO.md b/TODO.md
deleted file mode 100644
index 1594a6a6..00000000
--- a/TODO.md
+++ /dev/null
@@ -1,6 +0,0 @@
-[x] Delete all stores we can
-[x] Create commands for each action
-[x] Finish timeline store
-[x] Migrate final timeline store to manager
-[x] Delete timeline store
-[ ] Update usage of all stores to use the new managers
\ No newline at end of file
diff --git a/apps/web/src/app/api/sounds/search/route.ts b/apps/web/src/app/api/sounds/search/route.ts
index a477c9db..b7356680 100644
--- a/apps/web/src/app/api/sounds/search/route.ts
+++ b/apps/web/src/app/api/sounds/search/route.ts
@@ -96,7 +96,7 @@ function buildSortParameter({ query, sort }: { query?: string; sort: string }) {
function applyEffectsFilters({
params,
min_rating,
- commercial_only
+ commercial_only,
}: {
params: URLSearchParams;
min_rating: number;
@@ -108,17 +108,19 @@ function applyEffectsFilters({
if (commercial_only) {
params.append(
"filter",
- 'license:("Attribution" OR "Creative Commons 0" OR "Attribution Noncommercial" OR "Attribution Commercial")'
+ 'license:("Attribution" OR "Creative Commons 0" OR "Attribution Noncommercial" OR "Attribution Commercial")',
);
}
params.append(
"filter",
- "tag:sound-effect OR tag:sfx OR tag:foley OR tag:ambient OR tag:nature OR tag:mechanical OR tag:electronic OR tag:impact OR tag:whoosh OR tag:explosion"
+ "tag:sound-effect OR tag:sfx OR tag:foley OR tag:ambient OR tag:nature OR tag:mechanical OR tag:electronic OR tag:impact OR tag:whoosh OR tag:explosion",
);
}
-function transformFreesoundResult(result: z.infer) {
+function transformFreesoundResult(
+ result: z.infer,
+) {
return {
id: result.id,
name: result.name,
@@ -169,7 +171,7 @@ export async function GET(request: NextRequest) {
error: "Invalid parameters",
details: validationResult.error.flatten().fieldErrors,
},
- { status: 400 }
+ { status: 400 },
);
}
@@ -190,7 +192,7 @@ export async function GET(request: NextRequest) {
message:
"Song search functionality is coming soon. Try searching for sound effects instead.",
},
- { status: 501 }
+ { status: 501 },
);
}
@@ -220,7 +222,7 @@ export async function GET(request: NextRequest) {
console.error("Freesound API error:", response.status, errorText);
return NextResponse.json(
{ error: "Failed to search sounds" },
- { status: response.status }
+ { status: response.status },
);
}
@@ -230,11 +232,11 @@ export async function GET(request: NextRequest) {
if (!freesoundValidation.success) {
console.error(
"Invalid Freesound API response:",
- freesoundValidation.error
+ freesoundValidation.error,
);
return NextResponse.json(
{ error: "Invalid response from Freesound API" },
- { status: 502 }
+ { status: 502 },
);
}
@@ -259,11 +261,11 @@ export async function GET(request: NextRequest) {
if (!responseValidation.success) {
console.error(
"Invalid API response structure:",
- responseValidation.error
+ responseValidation.error,
);
return NextResponse.json(
{ error: "Internal response formatting error" },
- { status: 500 }
+ { status: 500 },
);
}
@@ -272,7 +274,7 @@ export async function GET(request: NextRequest) {
console.error("Error searching sounds:", error);
return NextResponse.json(
{ error: "Internal server error" },
- { status: 500 }
+ { status: 500 },
);
}
-}
\ No newline at end of file
+}
diff --git a/apps/web/src/app/base-page.tsx b/apps/web/src/app/base-page.tsx
index 48303e8e..38eca161 100644
--- a/apps/web/src/app/base-page.tsx
+++ b/apps/web/src/app/base-page.tsx
@@ -30,7 +30,7 @@ export function BasePage({
-
+
{post.title}
{post.description}
diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx
index 7bba1023..2c62b554 100644
--- a/apps/web/src/app/projects/page.tsx
+++ b/apps/web/src/app/projects/page.tsx
@@ -190,7 +190,7 @@ export default function ProjectsPage() {
)}
-
+
@@ -419,16 +419,18 @@ function ProjectCard({
const cardContent = (
{isSelectionMode && (
-
-
+
+
@@ -462,7 +464,7 @@ function ProjectCard({
-
+
{project.name}
{!isSelectionMode && (
@@ -475,10 +477,11 @@ function ProjectCard({
aria-label="project options"
variant="text"
size="sm"
- className={`ml-2 size-6 shrink-0 p-0 transition-all ${isDropdownOpen
- ? "opacity-100"
- : "opacity-0 group-hover:opacity-100"
- }`}
+ className={`ml-2 size-6 shrink-0 p-0 transition-all ${
+ isDropdownOpen
+ ? "opacity-100"
+ : "opacity-0 group-hover:opacity-100"
+ }`}
onClick={(event) => event.preventDefault()}
>
diff --git a/apps/web/src/app/roadmap/page.tsx b/apps/web/src/app/roadmap/page.tsx
index 4ef18c72..f3ca1e4a 100644
--- a/apps/web/src/app/roadmap/page.tsx
+++ b/apps/web/src/app/roadmap/page.tsx
@@ -156,7 +156,7 @@ function RoadmapItem({ item, index }: { item: RoadmapItem; index: number }) {
return (
- {index + 1}
+ {index + 1}
{item.title}
diff --git a/apps/web/src/app/rss.xml/route.ts b/apps/web/src/app/rss.xml/route.ts
index fd697e02..19b9e2ea 100644
--- a/apps/web/src/app/rss.xml/route.ts
+++ b/apps/web/src/app/rss.xml/route.ts
@@ -14,8 +14,9 @@ export async function GET() {
language: "en",
image: `${SITE_INFO.openGraphImage}`,
favicon: `${SITE_INFO.favicon}`,
- copyright: `All rights reserved ${new Date().getFullYear()}, ${SITE_INFO.title
- }`,
+ copyright: `All rights reserved ${new Date().getFullYear()}, ${
+ SITE_INFO.title
+ }`,
});
for (const post of posts) {
diff --git a/apps/web/src/components/editor/assets-panel/drag-overlay.tsx b/apps/web/src/components/editor/assets-panel/drag-overlay.tsx
index e1fd257e..e1f72a30 100644
--- a/apps/web/src/components/editor/assets-panel/drag-overlay.tsx
+++ b/apps/web/src/components/editor/assets-panel/drag-overlay.tsx
@@ -27,15 +27,15 @@ export function MediaDragOverlay({
return (
-
+
-
+
{isProcessing
? `Processing your files (${progress}%)`
: "Drag and drop videos, photos, and audio files here"}
@@ -44,7 +44,7 @@ export function MediaDragOverlay({
{isProcessing && (
-
+
-
+
{tab.label}
@@ -99,7 +99,7 @@ function FadeOverlay({
return (
{
if (progress.status === "loading-model") {
- setProcessingStep(
- `Loading model ${Math.round(progress.progress)}%`,
- );
+ setProcessingStep(`Loading model ${Math.round(progress.progress)}%`);
} else if (progress.status === "transcribing") {
setProcessingStep("Transcribing...");
}
@@ -48,7 +46,8 @@ export function Captions() {
const result = await transcriptionService.transcribe({
audioData: samples,
- language: selectedLanguage === "auto" ? "auto" : selectedLanguage.toLowerCase(),
+ language:
+ selectedLanguage === "auto" ? "auto" : selectedLanguage.toLowerCase(),
onProgress: handleProgress,
});
diff --git a/apps/web/src/components/editor/assets-panel/views/media.tsx b/apps/web/src/components/editor/assets-panel/views/media.tsx
index 078242e5..211eea67 100644
--- a/apps/web/src/components/editor/assets-panel/views/media.tsx
+++ b/apps/web/src/components/editor/assets-panel/views/media.tsx
@@ -49,7 +49,8 @@ export function MediaView() {
const mediaFiles = editor.media.getAssets();
const activeProject = editor.project.getActive();
- const { mediaViewMode, setMediaViewMode, highlightMediaId, clearHighlight } = useAssetsPanelStore();
+ const { mediaViewMode, setMediaViewMode, highlightMediaId, clearHighlight } =
+ useAssetsPanelStore();
const { highlightedId, registerElement } = useRevealItem(
highlightMediaId,
clearHighlight,
@@ -509,7 +510,7 @@ function MediaDurationBadge({ duration }: { duration?: number }) {
if (!duration) return null;
return (
-
+
{formatDuration({ duration })}
);
@@ -551,7 +552,6 @@ function MediaTypePlaceholder({
}
function MediaPreview({ item }: { item: MediaAsset }) {
-
if (item.type === "image") {
return (
@@ -604,13 +604,7 @@ function MediaPreview({ item }: { item: MediaAsset }) {
);
}
- return (
-
- );
+ return
;
}
function SortMenuItem({
diff --git a/apps/web/src/components/editor/assets-panel/views/settings.tsx b/apps/web/src/components/editor/assets-panel/views/settings.tsx
index 8126ebb6..c4dbd33d 100644
--- a/apps/web/src/components/editor/assets-panel/views/settings.tsx
+++ b/apps/web/src/components/editor/assets-panel/views/settings.tsx
@@ -82,11 +82,7 @@ function ProjectSettingsTabs() {
);
}
-function getCurrentCanvasSize({
- activeProject,
-}: {
- activeProject: TProject;
-}) {
+function getCurrentCanvasSize({ activeProject }: { activeProject: TProject }) {
const { canvasSize } = activeProject.settings;
return {
@@ -155,9 +151,7 @@ function ProjectInfoView() {
Name
-
- {activeProject.metadata.name}
-
+ {activeProject.metadata.name}
@@ -237,7 +231,7 @@ const BlurPreview = memo(
style={{ filter: `blur(${blur.value}px)` }}
loading="eager"
/>
-
+
{blur.label}
@@ -270,18 +264,18 @@ const BackgroundPreviews = memo(
className={cn(
"border-foreground/15 hover:border-primary aspect-square w-full cursor-pointer rounded-sm border",
isColorBackground &&
- bg === currentBackgroundColor &&
- "border-primary border-2",
+ bg === currentBackgroundColor &&
+ "border-primary border-2",
)}
style={
useBackgroundColor
? { backgroundColor: bg }
: {
- background: bg,
- backgroundSize: "cover",
- backgroundPosition: "center",
- backgroundRepeat: "no-repeat",
- }
+ background: bg,
+ backgroundSize: "cover",
+ backgroundPosition: "center",
+ backgroundRepeat: "no-repeat",
+ }
}
onClick={() => handleColorSelect({ bg })}
/>
diff --git a/apps/web/src/components/editor/assets-panel/views/sounds.tsx b/apps/web/src/components/editor/assets-panel/views/sounds.tsx
index 83faf090..432e2a50 100644
--- a/apps/web/src/components/editor/assets-panel/views/sounds.tsx
+++ b/apps/web/src/components/editor/assets-panel/views/sounds.tsx
@@ -36,8 +36,8 @@ import { useInfiniteScroll } from "@/hooks/use-infinite-scroll";
export function SoundsView() {
return (
-
-
+
+
Sound effects
@@ -48,19 +48,19 @@ export function SoundsView() {
@@ -97,12 +97,15 @@ function SoundEffectsView() {
loadMore,
hasNextPage,
isLoadingMore,
- } = useSoundSearch({ query: searchQuery, commercialOnly: showCommercialOnly });
+ } = useSoundSearch({
+ query: searchQuery,
+ commercialOnly: showCommercialOnly,
+ });
// Audio playback state
const [playingId, setPlayingId] = useState(null);
const [audioElement, setAudioElement] = useState(
- null
+ null,
);
const { scrollAreaRef, handleScroll } = useInfiniteScroll({
@@ -125,7 +128,7 @@ function SoundEffectsView() {
}
const response = await fetch(
- "/api/sounds/search?page_size=50&sort=downloads"
+ "/api/sounds/search?page_size=50&sort=downloads",
);
if (!ignore) {
@@ -146,7 +149,9 @@ function SoundEffectsView() {
console.error("Failed to fetch top sounds:", error);
setError({
error:
- error instanceof Error ? error.message : "Failed to load sounds",
+ error instanceof Error
+ ? error.message
+ : "Failed to load sounds",
});
}
} finally {
@@ -221,7 +226,7 @@ function SoundEffectsView() {
};
return (
-
+
Show only commercially licensed
-
+
{showCommercialOnly
? "Only showing sounds licensed for commercial use"
: "Showing all sounds regardless of license"}
@@ -260,7 +265,7 @@ function SoundEffectsView() {
@@ -289,7 +294,7 @@ function SoundEffectsView() {
)}
{isLoadingMore && (
-
+
Loading more sounds...
)}
@@ -314,7 +319,7 @@ function SavedSoundsView() {
// Audio playback state
const [playingId, setPlayingId] = useState
(null);
const [audioElement, setAudioElement] = useState(
- null
+ null,
);
// Clear confirmation dialog state
@@ -378,7 +383,7 @@ function SavedSoundsView() {
if (isLoadingSavedSounds) {
return (
-
+
Loading saved sounds...
@@ -388,7 +393,7 @@ function SavedSoundsView() {
if (savedSoundsError) {
return (
-
+
Error: {savedSoundsError}
@@ -398,14 +403,14 @@ function SavedSoundsView() {
if (savedSounds.length === 0) {
return (
-
+
No saved sounds
-
+
Click the heart icon on any sound to save it here
@@ -414,9 +419,9 @@ function SavedSoundsView() {
}
return (
-
+
-
+
{savedSounds.length} saved{" "}
{savedSounds.length === 1 ? "sound" : "sounds"}
@@ -425,7 +430,7 @@ function SavedSoundsView() {
Clear all
@@ -457,7 +462,7 @@ function SavedSoundsView() {
-
+
{savedSounds.map((sound) => (
-
-
+
+
{isPlaying ? (
) : (
@@ -527,9 +532,9 @@ function AudioItem({
)}
-
-
{sound.name}
-
+
+
{sound.name}
+
{sound.username}
@@ -538,7 +543,7 @@ function AudioItem({
@@ -547,10 +552,11 @@ function AudioItem({
diff --git a/apps/web/src/components/editor/assets-panel/views/stickers.tsx b/apps/web/src/components/editor/assets-panel/views/stickers.tsx
index 807cc603..7cdebc0d 100644
--- a/apps/web/src/components/editor/assets-panel/views/stickers.tsx
+++ b/apps/web/src/components/editor/assets-panel/views/stickers.tsx
@@ -108,10 +108,7 @@ function StickerGrid({
};
return (
-
+
{icons.map((iconName) => (
No stickers found
-
{message}
+
{message}
);
@@ -211,17 +208,17 @@ function StickersContentView({ category }: { category: StickerCategory }) {
const collection = collections[c.prefix];
return collection
? {
- prefix: c.prefix,
- name: c.name,
- total: collection.total,
- }
+ prefix: c.prefix,
+ name: c.name,
+ total: collection.total,
+ }
: null;
})
.filter(Boolean) as Array<{
- prefix: string;
- name: string;
- total: number;
- }>;
+ prefix: string;
+ name: string;
+ total: number;
+ }>;
}, [collections, category]);
const { scrollAreaRef, handleScroll } = useInfiniteScroll({
@@ -485,7 +482,7 @@ function CollectionItem({ title, subtitle, onClick }: CollectionItemProps) {
return (
@@ -523,7 +520,7 @@ function StickerItem({
const preview = imageError ? (
-
+
{displayName}
@@ -534,10 +531,10 @@ function StickerItem({
hostIndex === 0
? getIconSvgUrl(iconName, { width: 64, height: 64 })
: buildIconSvgUrl(
- ICONIFY_HOSTS[Math.min(hostIndex, ICONIFY_HOSTS.length - 1)],
- iconName,
- { width: 64, height: 64 },
- )
+ ICONIFY_HOSTS[Math.min(hostIndex, ICONIFY_HOSTS.length - 1)],
+ iconName,
+ { width: 64, height: 64 },
+ )
}
alt={displayName}
width={64}
@@ -546,9 +543,9 @@ function StickerItem({
style={
capSize
? {
- maxWidth: "var(--sticker-max, 160px)",
- maxHeight: "var(--sticker-max, 160px)",
- }
+ maxWidth: "var(--sticker-max, 160px)",
+ maxHeight: "var(--sticker-max, 160px)",
+ }
: undefined
}
onError={() => {
diff --git a/apps/web/src/components/editor/assets-panel/views/text.tsx b/apps/web/src/components/editor/assets-panel/views/text.tsx
index 97ba9a29..db41364b 100644
--- a/apps/web/src/components/editor/assets-panel/views/text.tsx
+++ b/apps/web/src/components/editor/assets-panel/views/text.tsx
@@ -28,7 +28,7 @@ export function TextView() {
name="Default text"
preview={
- Default text
+ Default text
}
dragData={{
diff --git a/apps/web/src/components/editor/export-button.tsx b/apps/web/src/components/editor/export-button.tsx
index 00d728d1..b82495a1 100644
--- a/apps/web/src/components/editor/export-button.tsx
+++ b/apps/web/src/components/editor/export-button.tsx
@@ -52,11 +52,11 @@ export function ExportButton() {
}
}}
>
-
+
Export
-
@@ -263,7 +263,7 @@ function ExportPopover({
{ }}
+ onClick={() => {}}
>
Cancel
diff --git a/apps/web/src/components/editor/layout-guide-overlay.tsx b/apps/web/src/components/editor/layout-guide-overlay.tsx
index 2ea2c39f..8fe18eff 100644
--- a/apps/web/src/components/editor/layout-guide-overlay.tsx
+++ b/apps/web/src/components/editor/layout-guide-overlay.tsx
@@ -5,11 +5,11 @@ import Image from "next/image";
function TikTokGuide() {
return (
-
+
diff --git a/apps/web/src/components/editor/panel-base-view.tsx b/apps/web/src/components/editor/panel-base-view.tsx
index 7010cd35..8ab1f994 100644
--- a/apps/web/src/components/editor/panel-base-view.tsx
+++ b/apps/web/src/components/editor/panel-base-view.tsx
@@ -42,7 +42,7 @@ export function PanelBaseView({
ref,
}: PanelBaseViewProps) {
return (
-
+
{!tabs || tabs.length === 0 ? (
{children}
) : (
@@ -50,15 +50,15 @@ export function PanelBaseView({
defaultValue={defaultTab}
value={value}
onValueChange={onValueChange}
- className="flex flex-col h-full"
+ className="flex h-full flex-col"
>
-
+
{tabs.map((tab) => (
{tab.icon ? (
-
+
{tab.icon}
) : null}
@@ -73,7 +73,7 @@ export function PanelBaseView({
{tab.content}
diff --git a/apps/web/src/components/editor/properties-panel/index.tsx b/apps/web/src/components/editor/properties-panel/index.tsx
index 810624c1..abd42dff 100644
--- a/apps/web/src/components/editor/properties-panel/index.tsx
+++ b/apps/web/src/components/editor/properties-panel/index.tsx
@@ -9,7 +9,7 @@ import { useEditor } from "@/hooks/use-editor";
import { useElementSelection } from "@/hooks/timeline/element/use-element-selection";
export function PropertiesPanel() {
- const editor = useEditor();
+ const editor = useEditor();
const { selectedElements } = useElementSelection();
const elementsWithTracks = editor.timeline.getElementsWithTracks({
@@ -57,7 +57,7 @@ function EmptyView() {
/>
Itβs empty here
-
+
Click an element on the timeline to edit its properties
diff --git a/apps/web/src/components/editor/properties-panel/property-item.tsx b/apps/web/src/components/editor/properties-panel/property-item.tsx
index e2d714b3..aa8a5489 100644
--- a/apps/web/src/components/editor/properties-panel/property-item.tsx
+++ b/apps/web/src/components/editor/properties-panel/property-item.tsx
@@ -20,7 +20,7 @@ export function PropertyItem({
direction === "row"
? "items-center justify-between gap-6"
: "flex-col gap-1.5",
- className
+ className,
)}
>
{children}
@@ -36,7 +36,7 @@ export function PropertyItemLabel({
className?: string;
}) {
return (
-
+
{children}
);
@@ -72,7 +72,7 @@ export function PropertyGroup({
return (
setIsExpanded(!isExpanded)}
>
diff --git a/apps/web/src/components/editor/properties-panel/text-properties.tsx b/apps/web/src/components/editor/properties-panel/text-properties.tsx
index 4024703d..cd43f1b0 100644
--- a/apps/web/src/components/editor/properties-panel/text-properties.tsx
+++ b/apps/web/src/components/editor/properties-panel/text-properties.tsx
@@ -118,7 +118,11 @@ export function TextProperties({
});
};
- const handleTransparentToggle = ({ isTransparent }: { isTransparent: boolean }) => {
+ const handleTransparentToggle = ({
+ isTransparent,
+ }: {
+ isTransparent: boolean;
+ }) => {
const newColor = isTransparent ? "transparent" : lastSelectedColor.current;
editor.timeline.updateTextElement({
trackId,
@@ -146,7 +150,7 @@ export function TextProperties({
@@ -299,8 +305,7 @@ export function TextProperties({
{
editor.timeline.updateTextElement({
@@ -337,9 +342,11 @@ export function TextProperties({
value={opacityInput}
min={0}
max={100}
- onChange={(e) => handleOpacityChange({ value: e.target.value })}
+ onChange={(e) =>
+ handleOpacityChange({ value: e.target.value })
+ }
onBlur={handleOpacityBlur}
- className="bg-panel-accent h-7 w-12 rounded-sm text-center !text-xs [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
+ className="bg-panel-accent h-7 w-12 [appearance:textfield] rounded-sm text-center !text-xs [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
/>
@@ -353,12 +360,11 @@ export function TextProperties({
string:
element.backgroundColor === "transparent"
? lastSelectedColor.current.replace("#", "")
- : (element.backgroundColor).replace(
- "#",
- "",
- ),
+ : element.backgroundColor.replace("#", ""),
})}
- onChange={(color) => handleColorChange({ color: `#${color}` })}
+ onChange={(color) =>
+ handleColorChange({ color: `#${color}` })
+ }
containerRef={containerRef}
className={
element.backgroundColor === "transparent"
@@ -374,7 +380,8 @@ export function TextProperties({
size="icon"
onClick={() =>
handleTransparentToggle({
- isTransparent: element.backgroundColor !== "transparent",
+ isTransparent:
+ element.backgroundColor !== "transparent",
})
}
className="bg-panel-accent size-9 overflow-hidden rounded-full p-0"
@@ -383,7 +390,7 @@ export function TextProperties({
className={cn(
"text-foreground",
element.backgroundColor === "transparent" &&
- "text-primary",
+ "text-primary",
)}
/>
diff --git a/apps/web/src/components/editor/scenes-view.tsx b/apps/web/src/components/editor/scenes-view.tsx
index 91f05155..f7967898 100644
--- a/apps/web/src/components/editor/scenes-view.tsx
+++ b/apps/web/src/components/editor/scenes-view.tsx
@@ -147,11 +147,11 @@ export function ScenesView({ children }: { children: React.ReactNode }) {
className={cn(
"w-full justify-between font-normal",
currentScene?.id === scene.id &&
- !isSelectMode &&
- "border-primary !text-primary",
+ !isSelectMode &&
+ "border-primary !text-primary",
isSelectMode &&
- selectedScenes.has(scene.id) &&
- "bg-accent border-foreground/30",
+ selectedScenes.has(scene.id) &&
+ "bg-accent border-foreground/30",
)}
onClick={() => handleSceneSwitch(scene.id)}
>
@@ -159,8 +159,8 @@ export function ScenesView({ children }: { children: React.ReactNode }) {
{((isSelectMode && selectedScenes.has(scene.id)) ||
(!isSelectMode && currentScene?.id === scene.id)) && (
-
- )}
+
+ )}
))}
diff --git a/apps/web/src/components/editor/timeline/audio-waveform.tsx b/apps/web/src/components/editor/timeline/audio-waveform.tsx
index 59a315b9..b3c4c656 100644
--- a/apps/web/src/components/editor/timeline/audio-waveform.tsx
+++ b/apps/web/src/components/editor/timeline/audio-waveform.tsx
@@ -79,8 +79,7 @@ export function AudioWaveform({
} else {
try {
newWaveSurfer.destroy();
- } catch {
- }
+ } catch {}
return;
}
@@ -121,8 +120,7 @@ export function AudioWaveform({
requestAnimationFrame(() => {
try {
wsToDestroy.destroy();
- } catch {
- }
+ } catch {}
if (mounted) {
initWaveSurfer();
}
@@ -142,8 +140,7 @@ export function AudioWaveform({
requestAnimationFrame(() => {
try {
wsToDestroy.destroy();
- } catch {
- }
+ } catch {}
});
}
};
@@ -155,7 +152,7 @@ export function AudioWaveform({
className={`flex items-center justify-center ${className}`}
style={{ height }}
>
-
Audio unavailable
+
Audio unavailable
);
}
@@ -164,7 +161,7 @@ export function AudioWaveform({
{isLoading && (
- Loading...
+ Loading...
)}
handleBookmarkClick({ event })}
>
-
diff --git a/apps/web/src/components/editor/timeline/drag-line.tsx b/apps/web/src/components/editor/timeline/drag-line.tsx
index b7a5287b..0b2304a0 100644
--- a/apps/web/src/components/editor/timeline/drag-line.tsx
+++ b/apps/web/src/components/editor/timeline/drag-line.tsx
@@ -14,7 +14,7 @@ export function DragLine({ dropTarget, tracks, isVisible }: DragLineProps) {
return (
);
diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx
index 3db56acf..142c3536 100644
--- a/apps/web/src/components/editor/timeline/index.tsx
+++ b/apps/web/src/components/editor/timeline/index.tsx
@@ -1,7 +1,15 @@
"use client";
import { ScrollArea } from "@/components/ui/scroll-area";
-import { Eye, EyeOff, VolumeOff, Volume2, LucideIcon, EyeIcon, Trash2 } from "lucide-react";
+import {
+ Eye,
+ EyeOff,
+ VolumeOff,
+ Volume2,
+ LucideIcon,
+ EyeIcon,
+ Trash2,
+} from "lucide-react";
import {
ContextMenu,
ContextMenuContent,
@@ -249,7 +257,7 @@ export function Timeline() {
{tracks.length > 0 && (
@@ -378,7 +386,7 @@ export function Timeline() {
-
{canTracktHaveAudio(track) && track.muted
? "Unmute track"
: "Mute track"}
diff --git a/apps/web/src/components/editor/timeline/snap-indicator.tsx b/apps/web/src/components/editor/timeline/snap-indicator.tsx
index 938cf81d..f5583244 100644
--- a/apps/web/src/components/editor/timeline/snap-indicator.tsx
+++ b/apps/web/src/components/editor/timeline/snap-indicator.tsx
@@ -38,7 +38,7 @@ export function SnapIndicator({
return (
@@ -139,8 +135,9 @@ export function TimelineElement({
{(hasAudio ? isMuted : canElementBeHidden(element) && element.hidden) && (
-
+
{hasAudio ? (
) : (
@@ -342,7 +339,7 @@ function ResizeHandle({
const isLeft = side === "left";
return (
handleResizeStart({ e, elementId, side })}
>
@@ -383,7 +380,8 @@ function ElementContent({
}
if (element.type === "audio") {
- const audioBuffer = element.sourceType === "library" ? element.buffer : undefined;
+ const audioBuffer =
+ element.sourceType === "library" ? element.buffer : undefined;
const audioUrl =
element.sourceType === "library"
@@ -436,7 +434,7 @@ function ElementContent({
className={`relative size-full ${isSelected ? "bg-primary" : "bg-transparent"}`}
>
);
diff --git a/apps/web/src/components/editor/timeline/timeline-toolbar.tsx b/apps/web/src/components/editor/timeline/timeline-toolbar.tsx
index 01500985..fb93ba22 100644
--- a/apps/web/src/components/editor/timeline/timeline-toolbar.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-toolbar.tsx
@@ -52,13 +52,10 @@ export function TimelineToolbar({
const newZoomLevel =
direction === "in"
? Math.min(
- TIMELINE_CONSTANTS.ZOOM_MAX,
- zoomLevel + TIMELINE_CONSTANTS.ZOOM_STEP,
- )
- : Math.max(
- minZoom,
- zoomLevel - TIMELINE_CONSTANTS.ZOOM_STEP,
- );
+ TIMELINE_CONSTANTS.ZOOM_MAX,
+ zoomLevel + TIMELINE_CONSTANTS.ZOOM_STEP,
+ )
+ : Math.max(minZoom, zoomLevel - TIMELINE_CONSTANTS.ZOOM_STEP);
setZoomLevel({ zoom: newZoomLevel });
};
@@ -146,7 +143,7 @@ function ToolbarLeftSection() {
icon={
}
tooltip="Coming soon" /* separate audio */
disabled={true}
- onClick={({ event }) => { }}
+ onClick={({ event }) => {}}
/>
}
tooltip="Coming soon" /* freeze frame */
disabled={true}
- onClick={({ event }) => { }}
+ onClick={({ event }) => {}}
/>
{currentScene?.name || "No Scene"}
- { }}
- type="button"
- >
+ {}} type="button">
@@ -254,19 +248,28 @@ function ToolbarRightSection({
onZoomChange: (zoom: number) => void;
onZoom: (options: { direction: "in" | "out" }) => void;
}) {
- const { snappingEnabled, rippleEditingEnabled, toggleSnapping, toggleRippleEditing } = useTimelineStore();
+ const {
+ snappingEnabled,
+ rippleEditingEnabled,
+ toggleSnapping,
+ toggleRippleEditing,
+ } = useTimelineStore();
return (
}
+ icon={
+
+ }
tooltip="Auto snapping"
onClick={() => toggleSnapping()}
/>
}
+ icon={
+
+ }
tooltip="Ripple editing"
onClick={() => toggleRippleEditing()}
/>
diff --git a/apps/web/src/components/editor/timeline/timeline-track.tsx b/apps/web/src/components/editor/timeline/timeline-track.tsx
index 1e2bf7e9..a75dbb6e 100644
--- a/apps/web/src/components/editor/timeline/timeline-track.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-track.tsx
@@ -58,7 +58,7 @@ export function TimelineTrackContent({
});
const hasSelectedElements = track.elements.some((element) =>
- isElementSelected({ trackId: track.id, elementId: element.id })
+ isElementSelected({ trackId: track.id, elementId: element.id }),
);
return (
diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx
index 74b3268d..d9a30a57 100644
--- a/apps/web/src/components/header.tsx
+++ b/apps/web/src/components/header.tsx
@@ -115,7 +115,7 @@ export function Header() {
))}
{
e.preventDefault();
diff --git a/apps/web/src/components/keyboard-shortcuts-help.tsx b/apps/web/src/components/keyboard-shortcuts-help.tsx
index 9ac71b5a..2e09c1cb 100644
--- a/apps/web/src/components/keyboard-shortcuts-help.tsx
+++ b/apps/web/src/components/keyboard-shortcuts-help.tsx
@@ -52,11 +52,11 @@ export function KeyboardShortcutsHelp() {
// Auto-save the new keybinding
const conflict = validateKeybinding(
keyString,
- recordingShortcut.action
+ recordingShortcut.action,
);
if (conflict) {
toast.error(
- `Key "${keyString}" is already bound to "${conflict.existingAction}"`
+ `Key "${keyString}" is already bound to "${conflict.existingAction}"`,
);
setRecordingShortcut(null);
return;
@@ -110,10 +110,10 @@ export function KeyboardShortcutsHelp() {
Shortcuts
-
+
-
+
Keyboard Shortcuts
@@ -122,11 +122,11 @@ export function KeyboardShortcutsHelp() {
-
+
{categories.map((category) => (
-
+
{category}
@@ -153,7 +153,6 @@ export function KeyboardShortcutsHelp() {
-
);
}
@@ -204,7 +203,7 @@ function ShortcutItem({
})}
{index < displayKeys.length - 1 && (
- or
+ or
)}
))}
@@ -240,4 +239,4 @@ function EditableShortcutKey({
{children}
);
-}
\ No newline at end of file
+}
diff --git a/apps/web/src/components/landing/handlebars.tsx b/apps/web/src/components/landing/handlebars.tsx
index 0baec7dd..8a9abcef 100644
--- a/apps/web/src/components/landing/handlebars.tsx
+++ b/apps/web/src/components/landing/handlebars.tsx
@@ -111,12 +111,12 @@ export function Handlebars({ children }: HandlebarsProps) {
width > 0 ? (rightHandle / (width + 10)) * 100 : 0;
return (
-
+
+
-
-
+
+
The open source
Video Editor
-
+
A simple but powerful video editor that gets the job done. Works on
any platform.
-
+
Try early beta
diff --git a/apps/web/src/components/language-select.tsx b/apps/web/src/components/language-select.tsx
index feaefd4c..9d7a2130 100644
--- a/apps/web/src/components/language-select.tsx
+++ b/apps/web/src/components/language-select.tsx
@@ -19,7 +19,7 @@ interface LanguageSelectProps {
function FlagPreloader({ languages }: { languages: Language[] }) {
return (
-
+
{languages.map((language) => (
lang.code === selectedCountry
+ (lang) => lang.code === selectedCountry,
);
const handleSelect = ({
@@ -91,14 +91,14 @@ export function LanguageSelect({
};
return (
-
+
{!expanded ? (
) : (
-
+
onSelect({ code: language.code, e })}
>
{language.code === "auto" ? (
diff --git a/apps/web/src/components/storage-provider.tsx b/apps/web/src/components/storage-provider.tsx
index 8bce8018..e7661f70 100644
--- a/apps/web/src/components/storage-provider.tsx
+++ b/apps/web/src/components/storage-provider.tsx
@@ -49,7 +49,7 @@ export function StorageProvider({ children }: StorageProviderProps) {
if (!hasSupport) {
toast.warning(
- "Storage not fully supported. Some features may not work."
+ "Storage not fully supported. Some features may not work.",
);
}
diff --git a/apps/web/src/components/theme-toggle.tsx b/apps/web/src/components/theme-toggle.tsx
index b128b8a9..45229a12 100644
--- a/apps/web/src/components/theme-toggle.tsx
+++ b/apps/web/src/components/theme-toggle.tsx
@@ -11,7 +11,11 @@ interface ThemeToggleProps {
onToggle?: (e: React.MouseEvent) => void;
}
-export function ThemeToggle({ className, iconClassName, onToggle }: ThemeToggleProps) {
+export function ThemeToggle({
+ className,
+ iconClassName,
+ onToggle,
+}: ThemeToggleProps) {
const { theme, setTheme } = useTheme();
return (
diff --git a/apps/web/src/components/ui/accordion.tsx b/apps/web/src/components/ui/accordion.tsx
index 0ad1934e..145fcb4d 100644
--- a/apps/web/src/components/ui/accordion.tsx
+++ b/apps/web/src/components/ui/accordion.tsx
@@ -49,7 +49,7 @@ const AccordionContent = React.forwardRef<
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props}
>
- {children}
+ {children}
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
diff --git a/apps/web/src/components/ui/alert-dialog.tsx b/apps/web/src/components/ui/alert-dialog.tsx
index d2261aa8..186949c7 100644
--- a/apps/web/src/components/ui/alert-dialog.tsx
+++ b/apps/web/src/components/ui/alert-dialog.tsx
@@ -18,8 +18,8 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
@@ -52,7 +52,7 @@ const AlertDialogHeader = ({
@@ -66,7 +66,7 @@ const AlertDialogFooter = ({
@@ -91,7 +91,7 @@ const AlertDialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
));
@@ -119,7 +119,7 @@ const AlertDialogCancel = React.forwardRef<
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
- className
+ className,
)}
{...props}
/>
diff --git a/apps/web/src/components/ui/alert.tsx b/apps/web/src/components/ui/alert.tsx
index 69ceb617..3db09ad7 100644
--- a/apps/web/src/components/ui/alert.tsx
+++ b/apps/web/src/components/ui/alert.tsx
@@ -16,7 +16,7 @@ const alertVariants = cva(
defaultVariants: {
variant: "default",
},
- }
+ },
);
const Alert = React.forwardRef<
@@ -38,7 +38,7 @@ const AlertTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/src/components/ui/avatar.tsx b/apps/web/src/components/ui/avatar.tsx
index 30cd4f41..10859238 100644
--- a/apps/web/src/components/ui/avatar.tsx
+++ b/apps/web/src/components/ui/avatar.tsx
@@ -13,7 +13,7 @@ const Avatar = React.forwardRef<
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
- className
+ className,
)}
{...props}
/>
@@ -39,8 +39,8 @@ const AvatarFallback = React.forwardRef<
diff --git a/apps/web/src/components/ui/badge.tsx b/apps/web/src/components/ui/badge.tsx
index 51517a48..e1c2b556 100644
--- a/apps/web/src/components/ui/badge.tsx
+++ b/apps/web/src/components/ui/badge.tsx
@@ -20,12 +20,13 @@ const badgeVariants = cva(
defaultVariants: {
variant: "default",
},
- }
+ },
);
export interface BadgeProps
- extends React.HTMLAttributes,
- VariantProps { }
+ extends
+ React.HTMLAttributes,
+ VariantProps {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
diff --git a/apps/web/src/components/ui/breadcrumb.tsx b/apps/web/src/components/ui/breadcrumb.tsx
index c5d97078..7330deca 100644
--- a/apps/web/src/components/ui/breadcrumb.tsx
+++ b/apps/web/src/components/ui/breadcrumb.tsx
@@ -19,8 +19,8 @@ const BreadcrumbList = React.forwardRef<
@@ -50,7 +50,7 @@ const BreadcrumbLink = React.forwardRef<
return (
);
@@ -66,7 +66,7 @@ const BreadcrumbPage = React.forwardRef<
role="link"
aria-disabled="true"
aria-current="page"
- className={cn("font-normal text-foreground", className)}
+ className={cn("text-foreground font-normal", className)}
{...props}
/>
));
@@ -80,7 +80,7 @@ const BreadcrumbSeparator = ({
svg]:w-3.5 [&>svg]:h-3.5", className)}
+ className={cn("[&>svg]:h-3.5 [&>svg]:w-3.5", className)}
{...props}
>
{children ?? }
diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx
index 41b2b45b..7656ad84 100644
--- a/apps/web/src/components/ui/button.tsx
+++ b/apps/web/src/components/ui/button.tsx
@@ -41,8 +41,9 @@ const buttonVariants = cva(
);
export interface ButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
+ extends
+ React.ButtonHTMLAttributes,
+ VariantProps {
asChild?: boolean;
}
diff --git a/apps/web/src/components/ui/calendar.tsx b/apps/web/src/components/ui/calendar.tsx
index 11242ded..e20cdacd 100644
--- a/apps/web/src/components/ui/calendar.tsx
+++ b/apps/web/src/components/ui/calendar.tsx
@@ -27,7 +27,7 @@ function Calendar({
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
- "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
@@ -40,11 +40,11 @@ function Calendar({
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
props.mode === "range"
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
- : "[&:has([aria-selected])]:rounded-md"
+ : "[&:has([aria-selected])]:rounded-md",
),
day: cn(
buttonVariants({ variant: "text" }),
- "h-8 w-8 p-0 font-normal aria-selected:opacity-100"
+ "h-8 w-8 p-0 font-normal aria-selected:opacity-100",
),
day_range_start: "day-range-start",
day_range_end: "day-range-end",
diff --git a/apps/web/src/components/ui/card.tsx b/apps/web/src/components/ui/card.tsx
index 1f9dca2e..ed18d83d 100644
--- a/apps/web/src/components/ui/card.tsx
+++ b/apps/web/src/components/ui/card.tsx
@@ -8,7 +8,7 @@ const Card = React.forwardRef<
>(({ className, ...props }, ref) => (
));
@@ -32,7 +32,7 @@ const CardTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
));
@@ -44,7 +44,7 @@ const CardDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/src/components/ui/carousel.tsx b/apps/web/src/components/ui/carousel.tsx
index bd4b19e4..d5fc8dc5 100644
--- a/apps/web/src/components/ui/carousel.tsx
+++ b/apps/web/src/components/ui/carousel.tsx
@@ -56,14 +56,14 @@ const Carousel = React.forwardRef<
children,
...props
},
- ref
+ ref,
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
- plugins
+ plugins,
);
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false);
@@ -95,7 +95,7 @@ const Carousel = React.forwardRef<
scrollNext();
}
},
- [scrollPrev, scrollNext]
+ [scrollPrev, scrollNext],
);
React.useEffect(() => {
@@ -146,7 +146,7 @@ const Carousel = React.forwardRef<
);
- }
+ },
);
Carousel.displayName = "Carousel";
@@ -163,7 +163,7 @@ const CarouselContent = React.forwardRef<
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
- className
+ className,
)}
{...props}
/>
@@ -186,7 +186,7 @@ const CarouselItem = React.forwardRef<
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
- className
+ className,
)}
{...props}
/>
@@ -206,11 +206,11 @@ const CarouselPrevious = React.forwardRef<
variant={variant}
size={size}
className={cn(
- "absolute h-8 w-8 rounded-full",
+ "absolute h-8 w-8 rounded-full",
orientation === "horizontal"
- ? "-left-12 top-1/2 -translate-y-1/2"
+ ? "top-1/2 -left-12 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
- className
+ className,
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
@@ -237,9 +237,9 @@ const CarouselNext = React.forwardRef<
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
- ? "-right-12 top-1/2 -translate-y-1/2"
+ ? "top-1/2 -right-12 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
- className
+ className,
)}
disabled={!canScrollNext}
onClick={scrollNext}
diff --git a/apps/web/src/components/ui/chart.tsx b/apps/web/src/components/ui/chart.tsx
index 889444a4..92e3924c 100644
--- a/apps/web/src/components/ui/chart.tsx
+++ b/apps/web/src/components/ui/chart.tsx
@@ -57,8 +57,8 @@ const ChartContainer = React.forwardRef<
data-chart={chartId}
ref={ref}
className={cn(
- "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden",
- className
+ "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
+ className,
)}
{...props}
>
@@ -74,7 +74,7 @@ ChartContainer.displayName = "Chart";
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
- ([_, config]) => config.theme || config.color
+ ([_, config]) => config.theme || config.color,
);
if (!colorConfig.length) {
@@ -89,15 +89,15 @@ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
- .map(([key, itemConfig]) => {
- const color =
- itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
- itemConfig.color;
- return color ? ` --color-${key}: ${color};` : null;
- })
- .join("\n")}
+ .map(([key, itemConfig]) => {
+ const color =
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
+ itemConfig.color;
+ return color ? ` --color-${key}: ${color};` : null;
+ })
+ .join("\n")}
}
-`
+`,
)
.join("\n"),
}}
@@ -110,13 +110,13 @@ const ChartTooltip = RechartsPrimitive.Tooltip;
const ChartTooltipContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps
&
- React.ComponentProps<"div"> & {
- hideLabel?: boolean;
- hideIndicator?: boolean;
- indicator?: "line" | "dot" | "dashed";
- nameKey?: string;
- labelKey?: string;
- }
+ React.ComponentProps<"div"> & {
+ hideLabel?: boolean;
+ hideIndicator?: boolean;
+ indicator?: "line" | "dot" | "dashed";
+ nameKey?: string;
+ labelKey?: string;
+ }
>(
(
{
@@ -134,7 +134,7 @@ const ChartTooltipContent = React.forwardRef<
nameKey,
labelKey,
},
- ref
+ ref,
) => {
const { config } = useChart();
@@ -184,8 +184,8 @@ const ChartTooltipContent = React.forwardRef<
{nestLabel ? null : tooltipLabel}
@@ -199,8 +199,8 @@ const ChartTooltipContent = React.forwardRef<
svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
- indicator === "dot" && "items-center"
+ "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
+ indicator === "dot" && "items-center",
)}
>
{formatter && item?.value !== undefined && item.name ? (
@@ -220,7 +220,7 @@ const ChartTooltipContent = React.forwardRef<
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
- }
+ },
)}
style={
{
@@ -234,7 +234,7 @@ const ChartTooltipContent = React.forwardRef<
@@ -244,7 +244,7 @@ const ChartTooltipContent = React.forwardRef<
{item.value && (
-
+
{item.value.toLocaleString()}
)}
@@ -257,7 +257,7 @@ const ChartTooltipContent = React.forwardRef<
);
- }
+ },
);
ChartTooltipContent.displayName = "ChartTooltip";
@@ -266,14 +266,14 @@ const ChartLegend = RechartsPrimitive.Legend;
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
- Pick
& {
- hideIcon?: boolean;
- nameKey?: string;
- }
+ Pick & {
+ hideIcon?: boolean;
+ nameKey?: string;
+ }
>(
(
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
- ref
+ ref,
) => {
const { config } = useChart();
@@ -287,7 +287,7 @@ const ChartLegendContent = React.forwardRef<
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
- className
+ className,
)}
>
{payload.map((item) => {
@@ -298,7 +298,7 @@ const ChartLegendContent = React.forwardRef<
svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
+ "[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3",
)}
>
{itemConfig?.icon && !hideIcon ? (
@@ -317,7 +317,7 @@ const ChartLegendContent = React.forwardRef<
})}
);
- }
+ },
);
ChartLegendContent.displayName = "ChartLegend";
@@ -325,7 +325,7 @@ ChartLegendContent.displayName = "ChartLegend";
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
- key: string
+ key: string,
) {
if (typeof payload !== "object" || payload === null) {
return;
@@ -333,8 +333,8 @@ function getPayloadConfigFromPayload(
const payloadPayload =
"payload" in payload &&
- typeof payload.payload === "object" &&
- payload.payload !== null
+ typeof payload.payload === "object" &&
+ payload.payload !== null
? payload.payload
: undefined;
diff --git a/apps/web/src/components/ui/checkbox.tsx b/apps/web/src/components/ui/checkbox.tsx
index 1cfe5686..e91b672f 100644
--- a/apps/web/src/components/ui/checkbox.tsx
+++ b/apps/web/src/components/ui/checkbox.tsx
@@ -13,8 +13,8 @@ const Checkbox = React.forwardRef<
diff --git a/apps/web/src/components/ui/color-picker.tsx b/apps/web/src/components/ui/color-picker.tsx
index 9a014ce1..98b214f5 100644
--- a/apps/web/src/components/ui/color-picker.tsx
+++ b/apps/web/src/components/ui/color-picker.tsx
@@ -135,11 +135,11 @@ const ColorPicker = React.forwardRef(
const rect = saturationRef.current.getBoundingClientRect();
const x = Math.max(
0,
- Math.min(1, (e.clientX - rect.left) / rect.width)
+ Math.min(1, (e.clientX - rect.left) / rect.width),
);
const y = Math.max(
0,
- Math.min(1, (e.clientY - rect.top) / rect.height)
+ Math.min(1, (e.clientY - rect.top) / rect.height),
);
const newS = x;
const newV = 1 - y;
@@ -151,7 +151,7 @@ const ColorPicker = React.forwardRef(
const rect = hueRef.current.getBoundingClientRect();
const x = Math.max(
0,
- Math.min(1, (e.clientX - rect.left) / rect.width)
+ Math.min(1, (e.clientX - rect.left) / rect.width),
);
const newH = x * 360;
setInternalHue(newH);
@@ -231,14 +231,14 @@ const ColorPicker = React.forwardRef(
{
if (!isOpen && triggerRef.current && containerRef?.current) {
@@ -252,9 +252,9 @@ const ColorPicker = React.forwardRef
(
setIsOpen(!isOpen);
}}
/>
-
+
(
createPortal(
(
>
@@ -289,7 +289,7 @@ const ColorPicker = React.forwardRef
(
@@ -303,11 +303,11 @@ const ColorPicker = React.forwardRef(
/>
,
- document.body
+ document.body,
)}
);
- }
+ },
);
ColorPicker.displayName = "ColorPicker";
@@ -321,8 +321,9 @@ const ColorCircle = ({
color: string;
}) => (
@@ -26,7 +26,7 @@ const CommandDialog = ({ children, ...props }: DialogProps) => {
return (
-
+
{children}
@@ -43,8 +43,8 @@ const CommandInput = React.forwardRef<
@@ -59,7 +59,7 @@ const CommandList = React.forwardRef<
>(({ className, ...props }, ref) => (
));
@@ -86,8 +86,8 @@ const CommandGroup = React.forwardRef<
@@ -101,7 +101,7 @@ const CommandSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
));
@@ -114,8 +114,8 @@ const CommandItem = React.forwardRef<
@@ -130,8 +130,8 @@ const CommandShortcut = ({
return (
diff --git a/apps/web/src/components/ui/context-menu.tsx b/apps/web/src/components/ui/context-menu.tsx
index 69ce5288..f4c90dd2 100644
--- a/apps/web/src/components/ui/context-menu.tsx
+++ b/apps/web/src/components/ui/context-menu.tsx
@@ -31,7 +31,7 @@ const contextMenuItemVariants = cva(
defaultVariants: {
variant: "default",
},
- }
+ },
);
const ContextMenuSubTrigger = React.forwardRef<
@@ -47,7 +47,7 @@ const ContextMenuSubTrigger = React.forwardRef<
contextMenuItemVariants({ variant }),
"data-[state=open]:bg-accent data-[state=open]:opacity-65",
inset && "pl-8",
- className
+ className,
)}
{...props}
>
@@ -64,8 +64,8 @@ const ContextMenuSubContent = React.forwardRef<
@@ -80,9 +80,9 @@ const ContextMenuContent = React.forwardRef<
@@ -102,7 +102,7 @@ const ContextMenuItem = React.forwardRef<
className={cn(
contextMenuItemVariants({ variant }),
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -117,7 +117,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
>(({ className, children, checked, variant = "default", ...props }, ref) => (
@@ -140,7 +140,7 @@ const ContextMenuRadioItem = React.forwardRef<
>(({ className, children, variant = "default", ...props }, ref) => (
@@ -164,7 +164,7 @@ const ContextMenuLabel = React.forwardRef<
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -177,7 +177,7 @@ const ContextMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx
index 0cfef3d5..aa857ffe 100644
--- a/apps/web/src/components/ui/dialog.tsx
+++ b/apps/web/src/components/ui/dialog.tsx
@@ -22,8 +22,8 @@ const DialogOverlay = React.forwardRef<
@@ -39,8 +39,8 @@ const DialogContent = React.forwardRef<
{
e.stopPropagation();
@@ -49,7 +49,7 @@ const DialogContent = React.forwardRef<
{...props}
>
{children}
-
+
Close
@@ -63,10 +63,7 @@ const DialogHeader = ({
...props
}: React.HTMLAttributes) => (
);
@@ -79,7 +76,7 @@ const DialogFooter = ({
@@ -93,8 +90,8 @@ const DialogTitle = React.forwardRef<
@@ -107,7 +104,7 @@ const DialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/src/components/ui/draggable-item.tsx b/apps/web/src/components/ui/draggable-item.tsx
index be35df3b..5648c8ae 100644
--- a/apps/web/src/components/ui/draggable-item.tsx
+++ b/apps/web/src/components/ui/draggable-item.tsx
@@ -170,7 +170,7 @@ export function DraggableItem({
typeof document !== "undefined" &&
createPortal(
{preview}
@@ -212,7 +212,7 @@ function PlusButton({
{
diff --git a/apps/web/src/components/ui/drawer.tsx b/apps/web/src/components/ui/drawer.tsx
index a69e5d2b..712c301b 100644
--- a/apps/web/src/components/ui/drawer.tsx
+++ b/apps/web/src/components/ui/drawer.tsx
@@ -43,12 +43,12 @@ const DrawerContent = React.forwardRef<
-
+
{children}
@@ -84,8 +84,8 @@ const DrawerTitle = React.forwardRef<
@@ -98,7 +98,7 @@ const DrawerDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/src/components/ui/dropdown-menu.tsx b/apps/web/src/components/ui/dropdown-menu.tsx
index de3a3682..ae35d6a4 100644
--- a/apps/web/src/components/ui/dropdown-menu.tsx
+++ b/apps/web/src/components/ui/dropdown-menu.tsx
@@ -31,7 +31,7 @@ const dropdownMenuItemVariants = cva(
defaultVariants: {
variant: "default",
},
- }
+ },
);
const DropdownMenuSubTrigger = React.forwardRef<
@@ -47,7 +47,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
dropdownMenuItemVariants({ variant }),
"data-[state=open]:bg-accent data-[state=open]:opacity-65",
inset && "pl-8",
- className
+ className,
)}
{...props}
>
@@ -65,8 +65,8 @@ const DropdownMenuSubContent = React.forwardRef<
@@ -87,9 +87,9 @@ const DropdownMenuContent = React.forwardRef<
e.preventDefault();
}}
className={cn(
- "z-50 min-w-32 overflow-hidden rounded-md border p-1 bg-popover text-popover-foreground shadow-md",
+ "bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ className,
)}
{...props}
/>
@@ -109,7 +109,7 @@ const DropdownMenuItem = React.forwardRef<
className={cn(
dropdownMenuItemVariants({ variant }),
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -126,8 +126,8 @@ const DropdownMenuCheckboxItem = React.forwardRef<
ref={ref}
className={cn(
dropdownMenuItemVariants({ variant }),
- "pl-2 pr-8",
- className
+ "pr-8 pl-2",
+ className,
)}
checked={checked}
onSelect={(e) => {
@@ -157,8 +157,8 @@ const DropdownMenuRadioItem = React.forwardRef<
ref={ref}
className={cn(
dropdownMenuItemVariants({ variant }),
- "pl-8 pr-2",
- className
+ "pr-2 pl-8",
+ className,
)}
{...props}
>
@@ -183,7 +183,7 @@ const DropdownMenuLabel = React.forwardRef<
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -196,7 +196,7 @@ const DropdownMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/src/components/ui/font-picker.tsx b/apps/web/src/components/ui/font-picker.tsx
index 7bb11274..2f2a85a0 100644
--- a/apps/web/src/components/ui/font-picker.tsx
+++ b/apps/web/src/components/ui/font-picker.tsx
@@ -20,7 +20,9 @@ export function FontPicker({
}: FontPickerProps) {
return (
-
+
diff --git a/apps/web/src/components/ui/form.tsx b/apps/web/src/components/ui/form.tsx
index 6dd46bcf..0995bcac 100644
--- a/apps/web/src/components/ui/form.tsx
+++ b/apps/web/src/components/ui/form.tsx
@@ -25,7 +25,7 @@ type FormFieldContextValue<
};
const FormFieldContext = React.createContext(
- {} as FormFieldContextValue
+ {} as FormFieldContextValue,
);
const FormField = <
@@ -69,7 +69,7 @@ type FormItemContextValue = {
};
const FormItemContext = React.createContext(
- {} as FormItemContextValue
+ {} as FormItemContextValue,
);
const FormItem = React.forwardRef<
@@ -134,7 +134,7 @@ const FormDescription = React.forwardRef<
);
@@ -156,7 +156,7 @@ const FormMessage = React.forwardRef<
{body}
diff --git a/apps/web/src/components/ui/hover-card.tsx b/apps/web/src/components/ui/hover-card.tsx
index 6dc10300..3dcd2cad 100644
--- a/apps/web/src/components/ui/hover-card.tsx
+++ b/apps/web/src/components/ui/hover-card.tsx
@@ -18,8 +18,8 @@ const HoverCardContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
- "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 rounded-md border p-4 shadow-md outline-hidden",
+ className,
)}
{...props}
/>
diff --git a/apps/web/src/components/ui/input-otp.tsx b/apps/web/src/components/ui/input-otp.tsx
index a92e135c..262a06bb 100644
--- a/apps/web/src/components/ui/input-otp.tsx
+++ b/apps/web/src/components/ui/input-otp.tsx
@@ -14,7 +14,7 @@ const InputOTP = React.forwardRef<
ref={ref}
containerClassName={cn(
"flex items-center justify-between gap-2 w-full has-disabled:opacity-50",
- containerClassName
+ containerClassName,
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
@@ -28,7 +28,7 @@ const InputOTPGroup = React.forwardRef<
>(({ className, ...props }, ref) => (
));
@@ -45,16 +45,16 @@ const InputOTPSlot = React.forwardRef<
{char}
{hasFakeCaret && (
)}
diff --git a/apps/web/src/components/ui/input-with-back.tsx b/apps/web/src/components/ui/input-with-back.tsx
index c4d1fed4..fa990185 100644
--- a/apps/web/src/components/ui/input-with-back.tsx
+++ b/apps/web/src/components/ui/input-with-back.tsx
@@ -41,7 +41,7 @@ export function InputWithBack({
return (
@@ -74,10 +74,10 @@ export function InputWithBack({
}}
transition={smoothTransition}
>
-
+
onChange?.(e.target.value)}
/>
diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx
index 694cfdb1..e459a4f7 100644
--- a/apps/web/src/components/ui/input.tsx
+++ b/apps/web/src/components/ui/input.tsx
@@ -29,7 +29,7 @@ const Input = forwardRef(
onBlur,
...props
},
- ref
+ ref,
) => {
const [isFocused, setIsFocused] = useState(false);
@@ -49,15 +49,17 @@ const Input = forwardRef(
iconCount === 2 ? "pr-20" : iconCount === 1 ? "pr-10" : "";
return (
-
+
(
e.preventDefault();
onClear?.();
}}
- className="absolute right-0 top-0 h-full px-3 text-muted-foreground !opacity-100"
+ className="text-muted-foreground absolute top-0 right-0 h-full px-3 !opacity-100"
aria-label="Clear input"
>
@@ -93,8 +95,8 @@ const Input = forwardRef(
size="icon"
onClick={() => onShowPasswordChange?.(!showPassword)}
className={cn(
- "absolute top-0 h-full px-3 text-muted-foreground hover:text-foreground",
- showClear ? "right-10" : "right-0"
+ "text-muted-foreground hover:text-foreground absolute top-0 h-full px-3",
+ showClear ? "right-10" : "right-0",
)}
aria-label={showPassword ? "Hide password" : "Show password"}
>
@@ -107,7 +109,7 @@ const Input = forwardRef(
)}
);
- }
+ },
);
Input.displayName = "Input";
diff --git a/apps/web/src/components/ui/label.tsx b/apps/web/src/components/ui/label.tsx
index 7f1f4307..56eed6f6 100644
--- a/apps/web/src/components/ui/label.tsx
+++ b/apps/web/src/components/ui/label.tsx
@@ -7,13 +7,13 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/utils/ui";
const labelVariants = cva(
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
);
const Label = React.forwardRef<
React.ElementRef
,
React.ComponentPropsWithoutRef &
- VariantProps
+ VariantProps
>(({ className, ...props }, ref) => (
@@ -38,8 +38,8 @@ const MenubarTrigger = React.forwardRef<
@@ -55,9 +55,9 @@ const MenubarSubTrigger = React.forwardRef<
@@ -74,8 +74,8 @@ const MenubarSubContent = React.forwardRef<
@@ -88,7 +88,7 @@ const MenubarContent = React.forwardRef<
>(
(
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
- ref
+ ref,
) => (
- )
+ ),
);
MenubarContent.displayName = MenubarPrimitive.Content.displayName;
@@ -116,9 +116,9 @@ const MenubarItem = React.forwardRef<
@@ -132,8 +132,8 @@ const MenubarCheckboxItem = React.forwardRef<
@@ -181,7 +181,7 @@ const MenubarLabel = React.forwardRef<
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -194,7 +194,7 @@ const MenubarSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
));
@@ -207,8 +207,8 @@ const MenubarShortcut = ({
return (
diff --git a/apps/web/src/components/ui/navigation-menu.tsx b/apps/web/src/components/ui/navigation-menu.tsx
index 147f628b..833181f5 100644
--- a/apps/web/src/components/ui/navigation-menu.tsx
+++ b/apps/web/src/components/ui/navigation-menu.tsx
@@ -13,7 +13,7 @@ const NavigationMenu = React.forwardRef<
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
- className
+ className,
)}
{...props}
>
@@ -31,7 +31,7 @@ const NavigationMenuList = React.forwardRef<
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
- className
+ className,
)}
{...props}
/>
@@ -41,7 +41,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva(
- "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 data-active:bg-accent/50 data-[state=open]:bg-accent/50"
+ "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 data-active:bg-accent/50 data-[state=open]:bg-accent/50",
);
const NavigationMenuTrigger = React.forwardRef<
@@ -69,8 +69,8 @@ const NavigationMenuContent = React.forwardRef<
@@ -83,11 +83,11 @@ const NavigationMenuViewport = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
+
-
+
));
NavigationMenuIndicator.displayName =
diff --git a/apps/web/src/components/ui/pagination.tsx b/apps/web/src/components/ui/pagination.tsx
index e7431134..f7f7ee2b 100644
--- a/apps/web/src/components/ui/pagination.tsx
+++ b/apps/web/src/components/ui/pagination.tsx
@@ -52,7 +52,7 @@ const PaginationLink = ({
variant: isActive ? "outline" : "text",
size,
}),
- className
+ className,
)}
{...props}
/>
diff --git a/apps/web/src/components/ui/phone-input.tsx b/apps/web/src/components/ui/phone-input.tsx
index af81be17..f41b4863 100644
--- a/apps/web/src/components/ui/phone-input.tsx
+++ b/apps/web/src/components/ui/phone-input.tsx
@@ -52,7 +52,7 @@ const PhoneInput: React.ForwardRefExoticComponent =
{...props}
/>
);
- }
+ },
);
PhoneInput.displayName = "PhoneInput";
@@ -61,7 +61,7 @@ const InputComponent = React.forwardRef<
React.ComponentProps<"input">
>(({ className, ...props }, ref) => (
@@ -89,7 +89,7 @@ const CountrySelect = ({
@@ -120,7 +120,7 @@ const CountrySelect = ({
selectedCountry={selectedCountry}
onChange={onChange}
/>
- ) : null
+ ) : null,
)}
@@ -146,7 +146,7 @@ const CountrySelectOption = ({
onChange(country)}>
{countryName}
- {`+${RPNInput.getCountryCallingCode(country)}`}
+ {`+${RPNInput.getCountryCallingCode(country)}`}
@@ -158,7 +158,7 @@ const FlagComponent = ({ country, countryName }: RPNInput.FlagProps) => {
const Flag = flags[country];
return (
-
+
{Flag && }
);
diff --git a/apps/web/src/components/ui/popover.tsx b/apps/web/src/components/ui/popover.tsx
index ea3ac094..2984d71d 100644
--- a/apps/web/src/components/ui/popover.tsx
+++ b/apps/web/src/components/ui/popover.tsx
@@ -21,8 +21,8 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
- "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-[0_0_10px_rgba(0,0,0,0.15)] outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-[0_0_10px_rgba(0,0,0,0.15)] outline-hidden",
+ className,
)}
{...props}
/>
diff --git a/apps/web/src/components/ui/progress.tsx b/apps/web/src/components/ui/progress.tsx
index 01532fc8..67abf3f0 100644
--- a/apps/web/src/components/ui/progress.tsx
+++ b/apps/web/src/components/ui/progress.tsx
@@ -12,13 +12,13 @@ const Progress = React.forwardRef<
diff --git a/apps/web/src/components/ui/prose.tsx b/apps/web/src/components/ui/prose.tsx
index 048626f4..130b20bd 100644
--- a/apps/web/src/components/ui/prose.tsx
+++ b/apps/web/src/components/ui/prose.tsx
@@ -10,8 +10,8 @@ function Prose({ children, html, className }: ProseProps) {
return (
{html ?
: children}
diff --git a/apps/web/src/components/ui/radio-group.tsx b/apps/web/src/components/ui/radio-group.tsx
index 8231a1ba..49b3e3f0 100644
--- a/apps/web/src/components/ui/radio-group.tsx
+++ b/apps/web/src/components/ui/radio-group.tsx
@@ -28,13 +28,13 @@ const RadioGroupItem = React.forwardRef<
-
+
);
diff --git a/apps/web/src/components/ui/resizable.tsx b/apps/web/src/components/ui/resizable.tsx
index 67f14b17..c78d02b3 100644
--- a/apps/web/src/components/ui/resizable.tsx
+++ b/apps/web/src/components/ui/resizable.tsx
@@ -11,7 +11,7 @@ const ResizablePanelGroup = ({
@@ -28,8 +28,8 @@ const ResizableHandle = ({
}) => (
div]:rotate-90",
- className
+ "focus-visible:ring-ring relative flex w-px items-center justify-center bg-transparent after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
+ className,
)}
{...props}
/>
diff --git a/apps/web/src/components/ui/scroll-area.tsx b/apps/web/src/components/ui/scroll-area.tsx
index 5b3f9f4c..6d0074eb 100644
--- a/apps/web/src/components/ui/scroll-area.tsx
+++ b/apps/web/src/components/ui/scroll-area.tsx
@@ -7,7 +7,7 @@ const ScrollArea = React.forwardRef<
>(({ className, children, ...props }, ref) => (
{children}
diff --git a/apps/web/src/components/ui/select.tsx b/apps/web/src/components/ui/select.tsx
index 932a82b8..30c023e1 100644
--- a/apps/web/src/components/ui/select.tsx
+++ b/apps/web/src/components/ui/select.tsx
@@ -25,7 +25,7 @@ const selectItemVariants = cva(
defaultVariants: {
variant: "default",
},
- }
+ },
);
const SelectTrigger = React.forwardRef<
@@ -35,8 +35,8 @@ const SelectTrigger = React.forwardRef<
span]:line-clamp-1",
- className
+ "bg-accent ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-7 w-auto cursor-pointer items-center justify-between gap-1 rounded-md px-3 py-2 text-sm whitespace-nowrap focus:ring-1 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
+ className,
)}
{...props}
>
@@ -56,7 +56,7 @@ const SelectScrollUpButton = React.forwardRef<
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
- className
+ className,
)}
{...props}
>
@@ -73,7 +73,7 @@ const SelectScrollDownButton = React.forwardRef<
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
- className
+ className,
)}
{...props}
>
@@ -91,10 +91,10 @@ const SelectContent = React.forwardRef<
{
@@ -108,7 +108,7 @@ const SelectContent = React.forwardRef<
className={cn(
"p-1",
position === "popper" &&
- "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)"
+ "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)",
)}
>
{children}
@@ -158,7 +158,7 @@ const SelectSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/src/components/ui/separator.tsx b/apps/web/src/components/ui/separator.tsx
index 24852d77..825c0417 100644
--- a/apps/web/src/components/ui/separator.tsx
+++ b/apps/web/src/components/ui/separator.tsx
@@ -11,20 +11,20 @@ const Separator = React.forwardRef<
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
- ref
+ ref,
) => (
- )
+ ),
);
Separator.displayName = SeparatorPrimitive.Root.displayName;
diff --git a/apps/web/src/components/ui/sheet.tsx b/apps/web/src/components/ui/sheet.tsx
index 83c10f12..42acdf5a 100644
--- a/apps/web/src/components/ui/sheet.tsx
+++ b/apps/web/src/components/ui/sheet.tsx
@@ -21,8 +21,8 @@ const SheetOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
,
- VariantProps { }
+ extends
+ React.ComponentPropsWithoutRef,
+ VariantProps {}
const SheetContent = React.forwardRef<
React.ElementRef,
@@ -68,7 +69,7 @@ const SheetContent = React.forwardRef<
}}
{...props}
>
-
+
Close
@@ -85,7 +86,7 @@ const SheetHeader = ({
@@ -99,7 +100,7 @@ const SheetFooter = ({
@@ -112,7 +113,7 @@ const SheetTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
));
@@ -124,7 +125,7 @@ const SheetDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/src/components/ui/skeleton.tsx b/apps/web/src/components/ui/skeleton.tsx
index 6f7f489d..227df5ba 100644
--- a/apps/web/src/components/ui/skeleton.tsx
+++ b/apps/web/src/components/ui/skeleton.tsx
@@ -6,7 +6,7 @@ function Skeleton({
}: React.HTMLAttributes) {
return (
);
diff --git a/apps/web/src/components/ui/slider.tsx b/apps/web/src/components/ui/slider.tsx
index e355ef51..dba25f69 100644
--- a/apps/web/src/components/ui/slider.tsx
+++ b/apps/web/src/components/ui/slider.tsx
@@ -12,15 +12,15 @@ const Slider = React.forwardRef<
-
-
+
+
-
+
));
Slider.displayName = SliderPrimitive.Root.displayName;
diff --git a/apps/web/src/components/ui/split-button.tsx b/apps/web/src/components/ui/split-button.tsx
index 6f75c0c2..ac7cfe04 100644
--- a/apps/web/src/components/ui/split-button.tsx
+++ b/apps/web/src/components/ui/split-button.tsx
@@ -18,15 +18,15 @@ const SplitButton = forwardRef(
{children}
);
- }
+ },
);
SplitButton.displayName = "SplitButton";
@@ -39,18 +39,18 @@ const SplitButtonSide = forwardRef<
ref={ref}
variant="text"
className={cn(
- "h-full rounded-none bg-panel-accent !opacity-100 border-0 gap-0 font-normal transition-colors disabled:text-muted-foreground",
+ "bg-panel-accent disabled:text-muted-foreground h-full gap-0 rounded-none border-0 font-normal !opacity-100 transition-colors",
onClick
- ? "hover:bg-foreground/10 hover:opacity-100 cursor-pointer"
+ ? "hover:bg-foreground/10 cursor-pointer hover:opacity-100"
: "cursor-default select-text",
paddingClass,
- className
+ className,
)}
onClick={onClick}
{...props}
>
{typeof children === "string" ? (
- {children}
+ {children}
) : (
children
)}
@@ -62,14 +62,14 @@ SplitButtonSide.displayName = "SplitButtonSide";
const SplitButtonLeft = forwardRef(
({ ...props }, ref) => {
return ;
- }
+ },
);
SplitButtonLeft.displayName = "SplitButtonLeft";
const SplitButtonRight = forwardRef(
({ ...props }, ref) => {
return ;
- }
+ },
);
SplitButtonRight.displayName = "SplitButtonRight";
@@ -79,11 +79,11 @@ const SplitButtonSeparator = forwardRef(
);
- }
+ },
);
SplitButtonSeparator.displayName = "SplitButtonSeparator";
diff --git a/apps/web/src/components/ui/switch.tsx b/apps/web/src/components/ui/switch.tsx
index 625e108a..13bd0c49 100644
--- a/apps/web/src/components/ui/switch.tsx
+++ b/apps/web/src/components/ui/switch.tsx
@@ -11,15 +11,15 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
diff --git a/apps/web/src/components/ui/table.tsx b/apps/web/src/components/ui/table.tsx
index 94f3243a..b0f61b9c 100644
--- a/apps/web/src/components/ui/table.tsx
+++ b/apps/web/src/components/ui/table.tsx
@@ -43,8 +43,8 @@ const TableFooter = React.forwardRef<
tr]:border-b-0",
- className
+ "bg-muted/50 border-t font-medium last:[&>tr]:border-b-0",
+ className,
)}
{...props}
/>
@@ -58,8 +58,8 @@ const TableRow = React.forwardRef<
@@ -73,8 +73,8 @@ const TableHead = React.forwardRef<
[role=checkbox]]:translate-y-[2px]",
- className
+ "text-muted-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
+ className,
)}
{...props}
/>
@@ -89,7 +89,7 @@ const TableCell = React.forwardRef<
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
- className
+ className,
)}
{...props}
/>
@@ -102,7 +102,7 @@ const TableCaption = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/src/components/ui/tabs.tsx b/apps/web/src/components/ui/tabs.tsx
index cede7525..763324c3 100644
--- a/apps/web/src/components/ui/tabs.tsx
+++ b/apps/web/src/components/ui/tabs.tsx
@@ -14,8 +14,8 @@ const TabsList = React.forwardRef<
@@ -29,8 +29,8 @@ const TabsTrigger = React.forwardRef<
@@ -44,8 +44,8 @@ const TabsContent = React.forwardRef<
diff --git a/apps/web/src/components/ui/textarea.tsx b/apps/web/src/components/ui/textarea.tsx
index fcb74c42..40cb1ada 100644
--- a/apps/web/src/components/ui/textarea.tsx
+++ b/apps/web/src/components/ui/textarea.tsx
@@ -9,10 +9,10 @@ const Textarea = React.forwardRef<
return (
@@ -37,13 +37,13 @@ const toastVariants = cva(
defaultVariants: {
variant: "default",
},
- }
+ },
);
const Toast = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef &
- VariantProps
+ VariantProps
>(({ className, variant, ...props }, ref) => {
return (
@@ -77,8 +77,8 @@ const ToastClose = React.forwardRef<
,
React.ComponentPropsWithoutRef &
- VariantProps
+ VariantProps
>(({ className, variant, size, children, ...props }, ref) => (
,
React.ComponentPropsWithoutRef &
- VariantProps
+ VariantProps
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext);
@@ -47,7 +47,7 @@ const ToggleGroupItem = React.forwardRef<
variant: context.variant || variant,
size: context.size || size,
}),
- className
+ className,
)}
{...props}
>
diff --git a/apps/web/src/components/ui/toggle.tsx b/apps/web/src/components/ui/toggle.tsx
index e0608cdb..7c88245d 100644
--- a/apps/web/src/components/ui/toggle.tsx
+++ b/apps/web/src/components/ui/toggle.tsx
@@ -25,13 +25,13 @@ const toggleVariants = cva(
variant: "default",
size: "default",
},
- }
+ },
);
const Toggle = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef &
- VariantProps
+ VariantProps
>(({ className, variant, size, ...props }, ref) => (
,
- VariantProps { }
+ extends
+ React.ComponentPropsWithoutRef,
+ VariantProps {}
const TooltipContent = React.forwardRef<
React.ElementRef,
@@ -52,16 +53,19 @@ const TooltipContent = React.forwardRef<
className={cn(tooltipVariants({ variant }), className)}
{...props}
>
- {variant === 'sidebar' && (
+ {variant === "sidebar" && (
-
+
)}
{props.children}
@@ -69,4 +73,4 @@ const TooltipContent = React.forwardRef<
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
\ No newline at end of file
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/apps/web/src/constants/stickers-constants.ts b/apps/web/src/constants/stickers-constants.ts
index 170d7db3..996dbea2 100644
--- a/apps/web/src/constants/stickers-constants.ts
+++ b/apps/web/src/constants/stickers-constants.ts
@@ -1,4 +1,9 @@
-export const STICKER_CATEGORIES = ["all", "general", "brands", "emoji"] as const;
+export const STICKER_CATEGORIES = [
+ "all",
+ "general",
+ "brands",
+ "emoji",
+] as const;
export const STICKER_CATEGORY_CONFIG: Record<
(typeof STICKER_CATEGORIES)[number],
diff --git a/apps/web/src/constants/timeline-constants.tsx b/apps/web/src/constants/timeline-constants.tsx
index 912d884d..34fed62d 100644
--- a/apps/web/src/constants/timeline-constants.tsx
+++ b/apps/web/src/constants/timeline-constants.tsx
@@ -1,10 +1,7 @@
import type { TrackType } from "@/types/timeline";
import { Video, TypeIcon, Music, Sticker } from "lucide-react";
-export const TRACK_COLORS: Record<
- TrackType,
- { background: string; }
-> = {
+export const TRACK_COLORS: Record = {
video: {
background: "transparent",
},
diff --git a/apps/web/src/constants/transcription-constants.ts b/apps/web/src/constants/transcription-constants.ts
index 9fc5f90f..abaa35d6 100644
--- a/apps/web/src/constants/transcription-constants.ts
+++ b/apps/web/src/constants/transcription-constants.ts
@@ -1,33 +1,37 @@
-import type { TranscriptionModel, TranscriptionModelId } from "@/types/transcription";
+import type {
+ TranscriptionModel,
+ TranscriptionModelId,
+} from "@/types/transcription";
export const TRANSCRIPTION_MODELS: TranscriptionModel[] = [
- {
- id: "whisper-tiny",
- name: "Tiny",
- huggingFaceId: "onnx-community/whisper-tiny",
- description: "Fastest, lower accuracy",
- },
- {
- id: "whisper-small",
- name: "Small",
- huggingFaceId: "onnx-community/whisper-small",
- description: "Good balance of speed and accuracy",
- },
- {
- id: "whisper-medium",
- name: "Medium",
- huggingFaceId: "onnx-community/whisper-medium",
- description: "Higher accuracy, slower",
- },
- {
- id: "whisper-large-v3-turbo",
- name: "Large v3 Turbo",
- huggingFaceId: "onnx-community/whisper-large-v3-turbo",
- description: "Best accuracy, requires WebGPU for good performance",
- },
+ {
+ id: "whisper-tiny",
+ name: "Tiny",
+ huggingFaceId: "onnx-community/whisper-tiny",
+ description: "Fastest, lower accuracy",
+ },
+ {
+ id: "whisper-small",
+ name: "Small",
+ huggingFaceId: "onnx-community/whisper-small",
+ description: "Good balance of speed and accuracy",
+ },
+ {
+ id: "whisper-medium",
+ name: "Medium",
+ huggingFaceId: "onnx-community/whisper-medium",
+ description: "Higher accuracy, slower",
+ },
+ {
+ id: "whisper-large-v3-turbo",
+ name: "Large v3 Turbo",
+ huggingFaceId: "onnx-community/whisper-large-v3-turbo",
+ description: "Best accuracy, requires WebGPU for good performance",
+ },
];
-export const DEFAULT_TRANSCRIPTION_MODEL: TranscriptionModelId = "whisper-small";
+export const DEFAULT_TRANSCRIPTION_MODEL: TranscriptionModelId =
+ "whisper-small";
export const DEFAULT_CHUNK_LENGTH_SECONDS = 30;
export const DEFAULT_STRIDE_SECONDS = 5;
diff --git a/apps/web/src/core/index.ts b/apps/web/src/core/index.ts
index 732faf29..03dd6374 100644
--- a/apps/web/src/core/index.ts
+++ b/apps/web/src/core/index.ts
@@ -41,5 +41,4 @@ export class EditorCore {
static reset(): void {
EditorCore.instance = null;
}
-
}
diff --git a/apps/web/src/core/managers/media-manager.ts b/apps/web/src/core/managers/media-manager.ts
index bbedad56..68ffe7c8 100644
--- a/apps/web/src/core/managers/media-manager.ts
+++ b/apps/web/src/core/managers/media-manager.ts
@@ -10,7 +10,7 @@ export class MediaManager {
private isLoading = false;
private listeners = new Set<() => void>();
- constructor(private editor: EditorCore) { }
+ constructor(private editor: EditorCore) {}
async addMediaAsset({
projectId,
diff --git a/apps/web/src/core/managers/project-manager.ts b/apps/web/src/core/managers/project-manager.ts
index 709bdf92..9aed3941 100644
--- a/apps/web/src/core/managers/project-manager.ts
+++ b/apps/web/src/core/managers/project-manager.ts
@@ -45,7 +45,7 @@ export class ProjectManager {
projectName: null,
};
- constructor(private editor: EditorCore) { }
+ constructor(private editor: EditorCore) {}
private async ensureStorageMigrations(): Promise {
if (this.storageMigrationPromise) {
@@ -384,8 +384,12 @@ export class ProjectManager {
const tracks = this.editor.timeline.getTracks();
const mediaAssets = this.editor.media.getAssets();
- const allElements = tracks.flatMap((track): TimelineElement[] => track.elements);
- const sortedElements = allElements.sort((a, b) => a.startTime - b.startTime);
+ const allElements = tracks.flatMap(
+ (track): TimelineElement[] => track.elements,
+ );
+ const sortedElements = allElements.sort(
+ (a, b) => a.startTime - b.startTime,
+ );
const firstElement = sortedElements[0];
if (
diff --git a/apps/web/src/core/managers/renderer-manager.ts b/apps/web/src/core/managers/renderer-manager.ts
index 35a8cb42..e0c70d96 100644
--- a/apps/web/src/core/managers/renderer-manager.ts
+++ b/apps/web/src/core/managers/renderer-manager.ts
@@ -9,7 +9,7 @@ export class RendererManager {
private renderTree: RootNode | null = null;
private listeners = new Set<() => void>();
- constructor(private editor: EditorCore) { }
+ constructor(private editor: EditorCore) {}
setRenderTree({ renderTree }: { renderTree: RootNode | null }): void {
this.renderTree = renderTree;
diff --git a/apps/web/src/core/managers/save-manager.ts b/apps/web/src/core/managers/save-manager.ts
index e81276c3..a8135aca 100644
--- a/apps/web/src/core/managers/save-manager.ts
+++ b/apps/web/src/core/managers/save-manager.ts
@@ -51,9 +51,7 @@ export class SaveManager {
}
}
- markDirty(
- { force = false }: { force?: boolean } = {},
- ): void {
+ markDirty({ force = false }: { force?: boolean } = {}): void {
if (this.isPaused && !force) return;
this.hasPendingSave = true;
this.queueSave();
diff --git a/apps/web/src/core/managers/scenes-manager.ts b/apps/web/src/core/managers/scenes-manager.ts
index 3694ca66..6d560b46 100644
--- a/apps/web/src/core/managers/scenes-manager.ts
+++ b/apps/web/src/core/managers/scenes-manager.ts
@@ -7,10 +7,7 @@ import {
canDeleteScene,
findCurrentScene,
} from "@/lib/scenes";
-import {
- getFrameTime,
- isBookmarkAtTime,
-} from "@/lib/timeline/bookmarks";
+import { getFrameTime, isBookmarkAtTime } from "@/lib/timeline/bookmarks";
import { ensureMainTrack } from "@/lib/timeline/track-utils";
import {
CreateSceneCommand,
@@ -25,7 +22,7 @@ export class ScenesManager {
private list: TScene[] = [];
private listeners = new Set<() => void>();
- constructor(private editor: EditorCore) { }
+ constructor(private editor: EditorCore) {}
async createScene({
name,
@@ -262,11 +259,7 @@ export class ScenesManager {
this.listeners.forEach((fn) => fn());
}
- updateSceneTracks({
- tracks,
- }: {
- tracks: TimelineTrack[];
- }): void {
+ updateSceneTracks({ tracks }: { tracks: TimelineTrack[] }): void {
if (!this.active) return;
const updatedScene: TScene = {
@@ -319,5 +312,4 @@ export class ScenesManager {
return { scenes: ensuredScenes, hasAddedMainTrack };
}
-
}
diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts
index 67280c8a..7268ff0d 100644
--- a/apps/web/src/core/managers/timeline-manager.ts
+++ b/apps/web/src/core/managers/timeline-manager.ts
@@ -31,7 +31,7 @@ import { InsertElementParams } from "@/lib/commands/timeline/element/insert-elem
export class TimelineManager {
private listeners = new Set<() => void>();
- constructor(private editor: EditorCore) { }
+ constructor(private editor: EditorCore) {}
addTrack({ type, index }: { type: TrackType; index?: number }): string {
const command = new AddTrackCommand(type, index);
@@ -44,10 +44,7 @@ export class TimelineManager {
this.editor.command.execute({ command });
}
- insertElement({
- element,
- placement,
- }: InsertElementParams): void {
+ insertElement({ element, placement }: InsertElementParams): void {
const command = new InsertElementCommand({ element, placement });
this.editor.command.execute({ command });
}
@@ -63,11 +60,7 @@ export class TimelineManager {
trimEnd: number;
pushHistory?: boolean;
}): void {
- const command = new UpdateElementTrimCommand(
- elementId,
- trimStart,
- trimEnd,
- );
+ const command = new UpdateElementTrimCommand(elementId, trimStart, trimEnd);
if (pushHistory) {
this.editor.command.execute({ command });
} else {
diff --git a/apps/web/src/hooks/timeline/element/use-element-interaction.ts b/apps/web/src/hooks/timeline/element/use-element-interaction.ts
index 27b12b60..31a900fc 100644
--- a/apps/web/src/hooks/timeline/element/use-element-interaction.ts
+++ b/apps/web/src/hooks/timeline/element/use-element-interaction.ts
@@ -124,7 +124,9 @@ function getDragDropTarget({
if (!containerRect || !scrollContainer) return null;
const sourceTrack = tracks.find(({ id }) => id === trackId);
- const movingElement = sourceTrack?.elements.find(({ id }) => id === elementId);
+ const movingElement = sourceTrack?.elements.find(
+ ({ id }) => id === elementId,
+ );
if (!movingElement) return null;
const elementDuration = movingElement.duration;
@@ -149,8 +151,10 @@ function getDragDropTarget({
});
}
-interface StartDragParams
- extends Omit {
+interface StartDragParams extends Omit<
+ ElementDragState,
+ "isDragging" | "currentTime" | "currentMouseY"
+> {
initialCurrentTime: number;
initialCurrentMouseY: number;
}
@@ -538,9 +542,7 @@ export function useElementInteraction({
// start drag
const clickOffsetTime = getClickOffsetTime({
clientX: event.clientX,
- elementRect: (
- event.currentTarget
- ).getBoundingClientRect(),
+ elementRect: event.currentTarget.getBoundingClientRect(),
zoomLevel,
});
pendingDragRef.current = {
diff --git a/apps/web/src/hooks/timeline/element/use-element-resize.ts b/apps/web/src/hooks/timeline/element/use-element-resize.ts
index 50c61ff7..62e4b52c 100644
--- a/apps/web/src/hooks/timeline/element/use-element-resize.ts
+++ b/apps/web/src/hooks/timeline/element/use-element-resize.ts
@@ -234,7 +234,8 @@ export function useTimelineElementResize({
if (newTrimEnd < 0) {
if (canExtendElementDuration()) {
const extensionNeeded = Math.abs(newTrimEnd);
- const baseDuration = resizing.initialDuration + resizing.initialTrimEnd;
+ const baseDuration =
+ resizing.initialDuration + resizing.initialTrimEnd;
const newDuration = snapTimeToFrame({
time: baseDuration + extensionNeeded,
fps: projectFps,
diff --git a/apps/web/src/hooks/timeline/use-edge-auto-scroll.ts b/apps/web/src/hooks/timeline/use-edge-auto-scroll.ts
index d1575584..14bb3e4a 100644
--- a/apps/web/src/hooks/timeline/use-edge-auto-scroll.ts
+++ b/apps/web/src/hooks/timeline/use-edge-auto-scroll.ts
@@ -47,7 +47,7 @@ export function useEdgeAutoScroll({
const intrinsicContentWidth = rulerViewport.scrollWidth;
const effectiveContentWidth = Math.max(
contentWidth,
- intrinsicContentWidth
+ intrinsicContentWidth,
);
const scrollMax = Math.max(0, effectiveContentWidth - viewportWidth);
@@ -63,7 +63,7 @@ export function useEdgeAutoScroll({
) {
const edgeDistance = Math.max(
0,
- viewportWidth - edgeThreshold - mouseXRelative
+ viewportWidth - edgeThreshold - mouseXRelative,
);
const intensity = 1 - edgeDistance / edgeThreshold;
scrollSpeed = maxScrollSpeed * intensity;
@@ -72,7 +72,7 @@ export function useEdgeAutoScroll({
if (scrollSpeed !== 0) {
const newScrollLeft = Math.max(
0,
- Math.min(scrollMax, rulerViewport.scrollLeft + scrollSpeed)
+ Math.min(scrollMax, rulerViewport.scrollLeft + scrollSpeed),
);
rulerViewport.scrollLeft = newScrollLeft;
tracksViewport.scrollLeft = newScrollLeft;
diff --git a/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts b/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts
index 1f2f89ed..fd19de7b 100644
--- a/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts
+++ b/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts
@@ -369,9 +369,9 @@ export function useTimelineDragDrop({
added.type === "audio" ? "audio" : "video";
const trackId = dropTarget.isNewTrack
? editor.timeline.addTrack({
- type: trackType,
- index: dropTarget.trackIndex,
- })
+ type: trackType,
+ index: dropTarget.trackIndex,
+ })
: currentTracks[dropTarget.trackIndex]?.id;
if (!trackId) return;
@@ -441,13 +441,7 @@ export function useTimelineDragDrop({
}
}
},
- [
- activeProject,
- editor.media,
- editor.timeline,
- currentTime,
- zoomLevel,
- ],
+ [activeProject, editor.media, editor.timeline, currentTime, zoomLevel],
);
const handleDrop = useCallback(
diff --git a/apps/web/src/hooks/timeline/use-timeline-interactions.ts b/apps/web/src/hooks/timeline/use-timeline-interactions.ts
index b761a278..ddb28581 100644
--- a/apps/web/src/hooks/timeline/use-timeline-interactions.ts
+++ b/apps/web/src/hooks/timeline/use-timeline-interactions.ts
@@ -132,7 +132,7 @@ export function useTimelineInteractions({
Math.min(
duration,
(mouseX + scrollLeft) /
- (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel),
+ (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel),
),
);
diff --git a/apps/web/src/hooks/timeline/use-timeline-snapping.ts b/apps/web/src/hooks/timeline/use-timeline-snapping.ts
index 576e3755..200f062a 100644
--- a/apps/web/src/hooks/timeline/use-timeline-snapping.ts
+++ b/apps/web/src/hooks/timeline/use-timeline-snapping.ts
@@ -101,9 +101,7 @@ export function useTimelineSnapping({
}
return {
- snappedTime: closestSnapPoint
- ? closestSnapPoint.time
- : targetTime,
+ snappedTime: closestSnapPoint ? closestSnapPoint.time : targetTime,
snapPoint: closestSnapPoint,
snapDistance: closestDistance,
};
diff --git a/apps/web/src/hooks/timeline/use-timeline-zoom.ts b/apps/web/src/hooks/timeline/use-timeline-zoom.ts
index 7d5195d8..cc2d8832 100644
--- a/apps/web/src/hooks/timeline/use-timeline-zoom.ts
+++ b/apps/web/src/hooks/timeline/use-timeline-zoom.ts
@@ -26,30 +26,33 @@ export function useTimelineZoom({
}: UseTimelineZoomProps): UseTimelineZoomReturn {
const [zoomLevel, setZoomLevel] = useState(1);
- const handleWheel = useCallback((event: ReactWheelEvent) => {
- const isZoomGesture = event.ctrlKey || event.metaKey;
- const isHorizontalScrollGesture =
- event.shiftKey || Math.abs(event.deltaX) > Math.abs(event.deltaY);
+ const handleWheel = useCallback(
+ (event: ReactWheelEvent) => {
+ const isZoomGesture = event.ctrlKey || event.metaKey;
+ const isHorizontalScrollGesture =
+ event.shiftKey || Math.abs(event.deltaX) > Math.abs(event.deltaY);
- if (isHorizontalScrollGesture) {
- return;
- }
+ if (isHorizontalScrollGesture) {
+ return;
+ }
- // pinch-zoom (ctrl/meta + wheel)
- if (isZoomGesture) {
- const zoomMultiplier = event.deltaY > 0 ? 1 / 1.1 : 1.1;
- setZoomLevel((prev) => {
- const nextZoom = Math.max(
- minZoom,
- Math.min(TIMELINE_CONSTANTS.ZOOM_MAX, prev * zoomMultiplier),
- );
- return nextZoom;
- });
- // for horizontal scrolling (when shift is held or horizontal wheel movement),
- // let the event bubble up to allow ScrollArea to handle it
- return;
- }
- }, [minZoom]);
+ // pinch-zoom (ctrl/meta + wheel)
+ if (isZoomGesture) {
+ const zoomMultiplier = event.deltaY > 0 ? 1 / 1.1 : 1.1;
+ setZoomLevel((prev) => {
+ const nextZoom = Math.max(
+ minZoom,
+ Math.min(TIMELINE_CONSTANTS.ZOOM_MAX, prev * zoomMultiplier),
+ );
+ return nextZoom;
+ });
+ // for horizontal scrolling (when shift is held or horizontal wheel movement),
+ // let the event bubble up to allow ScrollArea to handle it
+ return;
+ }
+ },
+ [minZoom],
+ );
useEffect(() => {
setZoomLevel((prev) => (prev < minZoom ? minZoom : prev));
diff --git a/apps/web/src/hooks/use-infinite-scroll.ts b/apps/web/src/hooks/use-infinite-scroll.ts
index 33f0aceb..87b189ed 100644
--- a/apps/web/src/hooks/use-infinite-scroll.ts
+++ b/apps/web/src/hooks/use-infinite-scroll.ts
@@ -28,7 +28,7 @@ export function useInfiniteScroll({
onLoadMore();
}
},
- [onLoadMore, hasMore, isLoading, threshold, enabled]
+ [onLoadMore, hasMore, isLoading, threshold, enabled],
);
return { scrollAreaRef, handleScroll };
diff --git a/apps/web/src/lib/blog/query.ts b/apps/web/src/lib/blog/query.ts
index a098bb93..47f08b72 100644
--- a/apps/web/src/lib/blog/query.ts
+++ b/apps/web/src/lib/blog/query.ts
@@ -16,12 +16,16 @@ const url =
process.env.NEXT_PUBLIC_MARBLE_API_URL ?? "https://api.marblecms.com";
const key = process.env.MARBLE_WORKSPACE_KEY ?? "cmd4iw9mm0006l804kwqv0k46";
-async function fetchFromMarble({ endpoint }: { endpoint: string }): Promise {
+async function fetchFromMarble({
+ endpoint,
+}: {
+ endpoint: string;
+}): Promise {
try {
const response = await fetch(`${url}/${key}/${endpoint}`);
if (!response.ok) {
throw new Error(
- `Failed to fetch ${endpoint}: ${response.status} ${response.statusText}`
+ `Failed to fetch ${endpoint}: ${response.status} ${response.statusText}`,
);
}
return (await response.json()) as T;
@@ -51,7 +55,11 @@ export async function getAuthors() {
return fetchFromMarble({ endpoint: "authors" });
}
-export async function processHtmlContent({ html }: { html: string }): Promise {
+export async function processHtmlContent({
+ html,
+}: {
+ html: string;
+}): Promise {
const processor = unified()
.use(rehypeSanitize)
.use(rehypeParse, { fragment: true })
diff --git a/apps/web/src/lib/commands/index.ts b/apps/web/src/lib/commands/index.ts
index d85f7772..6700087b 100644
--- a/apps/web/src/lib/commands/index.ts
+++ b/apps/web/src/lib/commands/index.ts
@@ -3,4 +3,4 @@ export { Command } from "./base-command";
export * from "./timeline";
export * from "./media";
export * from "./scene";
-export * from "./project";
\ No newline at end of file
+export * from "./project";
diff --git a/apps/web/src/lib/commands/scene/toggle-bookmark.ts b/apps/web/src/lib/commands/scene/toggle-bookmark.ts
index 0bd5ef3d..c06dc5df 100644
--- a/apps/web/src/lib/commands/scene/toggle-bookmark.ts
+++ b/apps/web/src/lib/commands/scene/toggle-bookmark.ts
@@ -2,10 +2,7 @@ import { Command } from "@/lib/commands/base-command";
import { EditorCore } from "@/core";
import type { TScene } from "@/types/timeline";
import { updateSceneInArray } from "@/lib/scenes";
-import {
- getFrameTime,
- toggleBookmarkInArray,
-} from "@/lib/timeline/bookmarks";
+import { getFrameTime, toggleBookmarkInArray } from "@/lib/timeline/bookmarks";
export class ToggleBookmarkCommand extends Command {
private savedScenes: TScene[] | null = null;
diff --git a/apps/web/src/lib/commands/timeline/element/delete-elements.ts b/apps/web/src/lib/commands/timeline/element/delete-elements.ts
index bd00ffde..b9933989 100644
--- a/apps/web/src/lib/commands/timeline/element/delete-elements.ts
+++ b/apps/web/src/lib/commands/timeline/element/delete-elements.ts
@@ -34,10 +34,7 @@ export class DeleteElementsCommand extends Command {
),
} as typeof track;
})
- .filter(
- (track) =>
- track.elements.length > 0 || isMainTrack(track),
- );
+ .filter((track) => track.elements.length > 0 || isMainTrack(track));
editor.timeline.updateTracks(updatedTracks);
}
diff --git a/apps/web/src/lib/commands/timeline/element/duplicate-elements.ts b/apps/web/src/lib/commands/timeline/element/duplicate-elements.ts
index 887f2da7..ce83cab2 100644
--- a/apps/web/src/lib/commands/timeline/element/duplicate-elements.ts
+++ b/apps/web/src/lib/commands/timeline/element/duplicate-elements.ts
@@ -59,7 +59,10 @@ export class DuplicateElementsCommand extends Command {
}
const newTrackId = generateUUID();
- const newTrackBase = buildEmptyTrack({ id: newTrackId, type: track.type });
+ const newTrackBase = buildEmptyTrack({
+ id: newTrackId,
+ type: track.type,
+ });
const newTrack = {
...newTrackBase,
elements: newTrackElements,
diff --git a/apps/web/src/lib/commands/timeline/element/index.ts b/apps/web/src/lib/commands/timeline/element/index.ts
index db82d77a..8b719afa 100644
--- a/apps/web/src/lib/commands/timeline/element/index.ts
+++ b/apps/web/src/lib/commands/timeline/element/index.ts
@@ -8,4 +8,4 @@ export { SplitElementsCommand } from "./split-elements";
export { UpdateTextElementCommand } from "./update-text-element";
export { ToggleElementsVisibilityCommand } from "./toggle-elements-visibility";
export { ToggleElementsMutedCommand } from "./toggle-elements-muted";
-export { MoveElementCommand } from "./move-elements";
\ No newline at end of file
+export { MoveElementCommand } from "./move-elements";
diff --git a/apps/web/src/lib/commands/timeline/element/insert-element.ts b/apps/web/src/lib/commands/timeline/element/insert-element.ts
index 26bd0cf8..96284827 100644
--- a/apps/web/src/lib/commands/timeline/element/insert-element.ts
+++ b/apps/web/src/lib/commands/timeline/element/insert-element.ts
@@ -28,7 +28,7 @@ type InsertElementPlacement =
export interface InsertElementParams {
element: CreateTimelineElement;
placement: InsertElementPlacement;
-};
+}
export class InsertElementCommand extends Command {
private elementId: string;
@@ -139,8 +139,7 @@ export class InsertElementCommand extends Command {
startTime: element.startTime,
trimStart: element.trimStart ?? 0,
trimEnd: element.trimEnd ?? 0,
- duration:
- element.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION,
+ duration: element.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION,
} as TimelineElement;
}
diff --git a/apps/web/src/lib/commands/timeline/element/move-elements.ts b/apps/web/src/lib/commands/timeline/element/move-elements.ts
index ffbc3699..1be137eb 100644
--- a/apps/web/src/lib/commands/timeline/element/move-elements.ts
+++ b/apps/web/src/lib/commands/timeline/element/move-elements.ts
@@ -1,6 +1,10 @@
import { Command } from "@/lib/commands/base-command";
import { EditorCore } from "@/core";
-import type { TimelineTrack, TimelineElement, TrackType } from "@/types/timeline";
+import type {
+ TimelineTrack,
+ TimelineElement,
+ TrackType,
+} from "@/types/timeline";
import {
buildEmptyTrack,
isMainTrack,
@@ -24,8 +28,12 @@ export class MoveElementCommand extends Command {
const editor = EditorCore.getInstance();
this.savedState = editor.timeline.getTracks();
- const sourceTrack = this.savedState.find((t) => t.id === this.sourceTrackId);
- const element = sourceTrack?.elements.find((el) => el.id === this.elementId);
+ const sourceTrack = this.savedState.find(
+ (t) => t.id === this.sourceTrackId,
+ );
+ const element = sourceTrack?.elements.find(
+ (el) => el.id === this.elementId,
+ );
if (!sourceTrack || !element) {
console.error("Source track or element not found");
diff --git a/apps/web/src/lib/db/schema.ts b/apps/web/src/lib/db/schema.ts
index c32d6c2a..ff90cd9f 100644
--- a/apps/web/src/lib/db/schema.ts
+++ b/apps/web/src/lib/db/schema.ts
@@ -51,9 +51,9 @@ export const verifications = pgTable("verifications", {
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").$defaultFn(
- () => /* @__PURE__ */ new Date()
+ () => /* @__PURE__ */ new Date(),
),
updatedAt: timestamp("updated_at").$defaultFn(
- () => /* @__PURE__ */ new Date()
+ () => /* @__PURE__ */ new Date(),
),
}).enableRLS();
diff --git a/apps/web/src/lib/gradients/canvas.ts b/apps/web/src/lib/gradients/canvas.ts
index e4f24343..acb3a3d7 100644
--- a/apps/web/src/lib/gradients/canvas.ts
+++ b/apps/web/src/lib/gradients/canvas.ts
@@ -1,46 +1,46 @@
import type {
- GradientAst,
- Color,
- ColorStop,
- GradientOrientation,
+ GradientAst,
+ Color,
+ ColorStop,
+ GradientOrientation,
} from "./parser";
import { parseGradient } from "./parser";
type BackgroundLayer =
- | { type: "color"; value: string }
- | { type: "gradient"; value: GradientAst };
+ | { type: "color"; value: string }
+ | { type: "gradient"; value: GradientAst };
type LinearPoints = {
- x0: number;
- y0: number;
- x1: number;
- y1: number;
- length: number;
+ x0: number;
+ y0: number;
+ x1: number;
+ y1: number;
+ length: number;
};
type RadialDimensions = {
- cx: number;
- cy: number;
- rx: number;
- ry: number;
+ cx: number;
+ cy: number;
+ rx: number;
+ ry: number;
};
type PositionKeyword = "left" | "center" | "right" | "top" | "bottom";
type Distance =
- | { type: "%"; value: string }
- | { type: "position-keyword"; value: string }
- | { type: "calc"; value: string }
- | { type: "px"; value: string }
- | { type: "em"; value: string };
+ | { type: "%"; value: string }
+ | { type: "position-keyword"; value: string }
+ | { type: "calc"; value: string }
+ | { type: "px"; value: string }
+ | { type: "em"; value: string };
type Position = { type: "position"; value: { x?: Distance; y?: Distance } };
type Shape = {
- type: "shape";
- value: "circle" | "ellipse";
- style?: Distance | { type: "extent-keyword"; value: string } | Position;
- at?: Position;
+ type: "shape";
+ value: "circle" | "ellipse";
+ style?: Distance | { type: "extent-keyword"; value: string } | Position;
+ at?: Position;
};
type DefaultRadial = { type: "default-radial"; at: Position };
@@ -50,857 +50,874 @@ type ExtentKeyword = { type: "extent-keyword"; value: string; at?: Position };
type RadialOrientation = Shape | ExtentKeyword | DefaultRadial;
const gradientLayerPattern =
- /^(?:\-(webkit|o|ms|moz)\-)?(linear-gradient|repeating-linear-gradient|radial-gradient|repeating-radial-gradient)/i;
+ /^(?:\-(webkit|o|ms|moz)\-)?(linear-gradient|repeating-linear-gradient|radial-gradient|repeating-radial-gradient)/i;
export function drawCssBackground({
- ctx,
- width,
- height,
- css,
+ ctx,
+ width,
+ height,
+ css,
}: {
- ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
- width: number;
- height: number;
- css: string;
+ ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
+ width: number;
+ height: number;
+ css: string;
}): void {
- const layers = parseBackgroundLayers({ css });
- const layersInPaintOrder = layers.slice().reverse();
+ const layers = parseBackgroundLayers({ css });
+ const layersInPaintOrder = layers.slice().reverse();
- for (const layer of layersInPaintOrder) {
- if (layer.type === "color") {
- ctx.fillStyle = layer.value;
- ctx.fillRect(0, 0, width, height);
- continue;
- }
-
- drawGradientLayer({ ctx, width, height, gradient: layer.value });
+ for (const layer of layersInPaintOrder) {
+ if (layer.type === "color") {
+ ctx.fillStyle = layer.value;
+ ctx.fillRect(0, 0, width, height);
+ continue;
}
+
+ drawGradientLayer({ ctx, width, height, gradient: layer.value });
+ }
}
-const parseBackgroundLayers = ({ css }: { css: string }): Array => {
- const segments = splitCssLayers({ css });
- const layers: Array = [];
+const parseBackgroundLayers = ({
+ css,
+}: {
+ css: string;
+}): Array => {
+ const segments = splitCssLayers({ css });
+ const layers: Array = [];
- for (const segment of segments) {
- if (!segment) {
- continue;
- }
-
- if (gradientLayerPattern.test(segment)) {
- try {
- const parsed = parseGradient({ code: segment.trim() });
- for (const gradient of parsed) {
- layers.push({ type: "gradient", value: gradient });
- }
- continue;
- } catch {
- layers.push({ type: "color", value: segment });
- continue;
- }
- }
-
- layers.push({ type: "color", value: segment });
+ for (const segment of segments) {
+ if (!segment) {
+ continue;
}
- return layers;
+ if (gradientLayerPattern.test(segment)) {
+ try {
+ const parsed = parseGradient({ code: segment.trim() });
+ for (const gradient of parsed) {
+ layers.push({ type: "gradient", value: gradient });
+ }
+ continue;
+ } catch {
+ layers.push({ type: "color", value: segment });
+ continue;
+ }
+ }
+
+ layers.push({ type: "color", value: segment });
+ }
+
+ return layers;
};
const splitCssLayers = ({ css }: { css: string }): Array => {
- const layers: Array = [];
- let current = "";
- let depth = 0;
+ const layers: Array = [];
+ let current = "";
+ let depth = 0;
- for (const char of css) {
- if (char === "(") {
- depth += 1;
- }
- if (char === ")") {
- depth = Math.max(0, depth - 1);
- }
-
- if (char === "," && depth === 0) {
- layers.push(current.trim());
- current = "";
- continue;
- }
-
- current += char;
+ for (const char of css) {
+ if (char === "(") {
+ depth += 1;
+ }
+ if (char === ")") {
+ depth = Math.max(0, depth - 1);
}
- if (current.trim()) {
- layers.push(current.trim());
+ if (char === "," && depth === 0) {
+ layers.push(current.trim());
+ current = "";
+ continue;
}
- return layers;
+ current += char;
+ }
+
+ if (current.trim()) {
+ layers.push(current.trim());
+ }
+
+ return layers;
};
const drawGradientLayer = ({
- ctx,
- width,
- height,
- gradient,
+ ctx,
+ width,
+ height,
+ gradient,
}: {
- ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
- width: number;
- height: number;
- gradient: GradientAst;
+ ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
+ width: number;
+ height: number;
+ gradient: GradientAst;
}): void => {
- if (gradient.type.includes("linear")) {
- drawLinearGradient({ ctx, width, height, gradient });
- return;
- }
+ if (gradient.type.includes("linear")) {
+ drawLinearGradient({ ctx, width, height, gradient });
+ return;
+ }
- drawRadialGradient({ ctx, width, height, gradient });
+ drawRadialGradient({ ctx, width, height, gradient });
};
const drawLinearGradient = ({
- ctx,
+ ctx,
+ width,
+ height,
+ gradient,
+}: {
+ ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
+ width: number;
+ height: number;
+ gradient: GradientAst;
+}): void => {
+ const { x0, y0, x1, y1, length } = resolveLinearPoints({
width,
height,
- gradient,
-}: {
- ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
- width: number;
- height: number;
- gradient: GradientAst;
-}): void => {
- const { x0, y0, x1, y1, length } = resolveLinearPoints({
- width,
- height,
- orientation: gradient.orientation,
- });
- const canvasGradient = ctx.createLinearGradient(x0, y0, x1, y1);
- const colorStops = normalizeColorStops({
- colorStops: gradient.colorStops,
- gradientLength: length,
- });
+ orientation: gradient.orientation,
+ });
+ const canvasGradient = ctx.createLinearGradient(x0, y0, x1, y1);
+ const colorStops = normalizeColorStops({
+ colorStops: gradient.colorStops,
+ gradientLength: length,
+ });
- for (const stop of colorStops) {
- canvasGradient.addColorStop(stop.offset, stop.color);
- }
+ for (const stop of colorStops) {
+ canvasGradient.addColorStop(stop.offset, stop.color);
+ }
- ctx.fillStyle = canvasGradient;
- ctx.fillRect(0, 0, width, height);
+ ctx.fillStyle = canvasGradient;
+ ctx.fillRect(0, 0, width, height);
};
const drawRadialGradient = ({
- ctx,
+ ctx,
+ width,
+ height,
+ gradient,
+}: {
+ ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
+ width: number;
+ height: number;
+ gradient: GradientAst;
+}): void => {
+ const { cx, cy, rx, ry } = resolveRadialDimensions({
width,
height,
- gradient,
-}: {
- ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
- width: number;
- height: number;
- gradient: GradientAst;
-}): void => {
- const { cx, cy, rx, ry } = resolveRadialDimensions({
- width,
- height,
- orientation: gradient.orientation,
- });
- const gradientLength = Math.max(rx, ry);
- const colorStops = normalizeColorStops({
- colorStops: gradient.colorStops,
- gradientLength,
- });
+ orientation: gradient.orientation,
+ });
+ const gradientLength = Math.max(rx, ry);
+ const colorStops = normalizeColorStops({
+ colorStops: gradient.colorStops,
+ gradientLength,
+ });
- if (rx === ry || ry === 0) {
- const canvasGradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, rx);
- for (const stop of colorStops) {
- canvasGradient.addColorStop(stop.offset, stop.color);
- }
- ctx.fillStyle = canvasGradient;
- ctx.fillRect(0, 0, width, height);
- return;
- }
-
- const scaleY = ry / rx;
- ctx.save();
- ctx.translate(cx, cy);
- ctx.scale(1, scaleY);
- const canvasGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, rx);
+ if (rx === ry || ry === 0) {
+ const canvasGradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, rx);
for (const stop of colorStops) {
- canvasGradient.addColorStop(stop.offset, stop.color);
+ canvasGradient.addColorStop(stop.offset, stop.color);
}
ctx.fillStyle = canvasGradient;
- ctx.fillRect(-cx, -cy / scaleY, width, height / scaleY);
- ctx.restore();
+ ctx.fillRect(0, 0, width, height);
+ return;
+ }
+
+ const scaleY = ry / rx;
+ ctx.save();
+ ctx.translate(cx, cy);
+ ctx.scale(1, scaleY);
+ const canvasGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, rx);
+ for (const stop of colorStops) {
+ canvasGradient.addColorStop(stop.offset, stop.color);
+ }
+ ctx.fillStyle = canvasGradient;
+ ctx.fillRect(-cx, -cy / scaleY, width, height / scaleY);
+ ctx.restore();
};
const resolveLinearPoints = ({
- width,
- height,
- orientation,
+ width,
+ height,
+ orientation,
}: {
- width: number;
- height: number;
- orientation: GradientOrientation | undefined;
+ width: number;
+ height: number;
+ orientation: GradientOrientation | undefined;
}): LinearPoints => {
- const angle = resolveLinearAngle({ orientation });
- const radians = (angle * Math.PI) / 180;
- const dx = Math.sin(radians);
- const dy = -Math.cos(radians);
- const centerX = width / 2;
- const centerY = height / 2;
- const halfLength = (Math.abs(width * dx) + Math.abs(height * dy)) / 2;
- const x0 = centerX - dx * halfLength;
- const y0 = centerY - dy * halfLength;
- const x1 = centerX + dx * halfLength;
- const y1 = centerY + dy * halfLength;
- const length = Math.hypot(x1 - x0, y1 - y0);
+ const angle = resolveLinearAngle({ orientation });
+ const radians = (angle * Math.PI) / 180;
+ const dx = Math.sin(radians);
+ const dy = -Math.cos(radians);
+ const centerX = width / 2;
+ const centerY = height / 2;
+ const halfLength = (Math.abs(width * dx) + Math.abs(height * dy)) / 2;
+ const x0 = centerX - dx * halfLength;
+ const y0 = centerY - dy * halfLength;
+ const x1 = centerX + dx * halfLength;
+ const y1 = centerY + dy * halfLength;
+ const length = Math.hypot(x1 - x0, y1 - y0);
- return { x0, y0, x1, y1, length };
+ return { x0, y0, x1, y1, length };
};
const resolveLinearAngle = ({
- orientation,
+ orientation,
}: {
- orientation: GradientOrientation | undefined;
+ orientation: GradientOrientation | undefined;
}): number => {
- if (!orientation) {
- return 180;
- }
-
- if (!Array.isArray(orientation)) {
- if (orientation.type === "angular") {
- return Number.parseFloat(orientation.value);
- }
-
- if (orientation.type === "directional") {
- return angleFromDirectional({ value: orientation.value });
- }
- }
-
+ if (!orientation) {
return 180;
+ }
+
+ if (!Array.isArray(orientation)) {
+ if (orientation.type === "angular") {
+ return Number.parseFloat(orientation.value);
+ }
+
+ if (orientation.type === "directional") {
+ return angleFromDirectional({ value: orientation.value });
+ }
+ }
+
+ return 180;
};
const angleFromDirectional = ({ value }: { value: string }): number => {
- const normalized = value.toLowerCase().replace("to", "").trim();
- const parts = normalized.split(/\s+/).filter(Boolean);
- let dx = 0;
- let dy = 0;
+ const normalized = value.toLowerCase().replace("to", "").trim();
+ const parts = normalized.split(/\s+/).filter(Boolean);
+ let dx = 0;
+ let dy = 0;
- for (const part of parts) {
- if (part === "left") {
- dx = -1;
- }
- if (part === "right") {
- dx = 1;
- }
- if (part === "top") {
- dy = -1;
- }
- if (part === "bottom") {
- dy = 1;
- }
+ for (const part of parts) {
+ if (part === "left") {
+ dx = -1;
}
-
- if (dx === 0 && dy === 0) {
- return 180;
+ if (part === "right") {
+ dx = 1;
}
+ if (part === "top") {
+ dy = -1;
+ }
+ if (part === "bottom") {
+ dy = 1;
+ }
+ }
- const angle = (Math.atan2(dx, -dy) * 180) / Math.PI;
- return (angle + 360) % 360;
+ if (dx === 0 && dy === 0) {
+ return 180;
+ }
+
+ const angle = (Math.atan2(dx, -dy) * 180) / Math.PI;
+ return (angle + 360) % 360;
};
const resolveRadialDimensions = ({
- width,
- height,
- orientation,
+ width,
+ height,
+ orientation,
}: {
- width: number;
- height: number;
- orientation: GradientOrientation | undefined;
+ width: number;
+ height: number;
+ orientation: GradientOrientation | undefined;
}): RadialDimensions => {
- const centerFallback = { cx: width / 2, cy: height / 2 };
- const radial = Array.isArray(orientation) ? (orientation[0] as RadialOrientation | undefined) : undefined;
+ const centerFallback = { cx: width / 2, cy: height / 2 };
+ const radial = Array.isArray(orientation)
+ ? (orientation[0] as RadialOrientation | undefined)
+ : undefined;
- if (!radial) {
- const { rx, ry } = resolveRadialExtents({
- width,
- height,
- cx: centerFallback.cx,
- cy: centerFallback.cy,
- shape: "ellipse",
- extent: "farthest-corner",
- });
- return { ...centerFallback, rx, ry };
- }
-
- if (radial.type === "shape") {
- const { cx, cy } = resolveRadialCenter({
- width,
- height,
- position: radial.at,
- });
- const shape = radial.value;
-
- if (radial.style && radial.style.type === "position") {
- const xDist = radial.style.value.x;
- const yDist = radial.style.value.y;
- const rx = xDist ? resolveEllipseDimension({ distance: xDist, axisSize: width }) : width / 2;
- const ry = yDist ? resolveEllipseDimension({ distance: yDist, axisSize: height }) : height / 2;
- return { cx, cy, rx, ry };
- }
-
- if (
- radial.style &&
- radial.style.type !== "extent-keyword"
- ) {
- const resolvedRadius = resolveDistanceInPixels({
- distance: radial.style,
- axisSize: Math.max(width, height),
- });
- if (shape === "circle") {
- return {
- cx,
- cy,
- rx: resolvedRadius ?? 0,
- ry: resolvedRadius ?? 0,
- };
- }
-
- const radius = resolvedRadius ?? 0;
- return { cx, cy, rx: radius, ry: radius };
- }
-
- const extent =
- radial.style && radial.style.type === "extent-keyword"
- ? radial.style.value
- : "farthest-corner";
- const { rx, ry } = resolveRadialExtents({
- width,
- height,
- cx,
- cy,
- shape,
- extent,
- });
- return { cx, cy, rx, ry };
- }
-
- if (radial.type === "extent-keyword") {
- const { cx, cy } = resolveRadialCenter({
- width,
- height,
- position: radial.at,
- });
- const { rx, ry } = resolveRadialExtents({
- width,
- height,
- cx,
- cy,
- shape: "ellipse",
- extent: radial.value,
- });
- return { cx, cy, rx, ry };
- }
-
- const { cx, cy } = resolveRadialCenter({
- width,
- height,
- position: radial.at,
- });
+ if (!radial) {
const { rx, ry } = resolveRadialExtents({
- width,
- height,
- cx,
- cy,
- shape: "ellipse",
- extent: "farthest-corner",
+ width,
+ height,
+ cx: centerFallback.cx,
+ cy: centerFallback.cy,
+ shape: "ellipse",
+ extent: "farthest-corner",
+ });
+ return { ...centerFallback, rx, ry };
+ }
+
+ if (radial.type === "shape") {
+ const { cx, cy } = resolveRadialCenter({
+ width,
+ height,
+ position: radial.at,
+ });
+ const shape = radial.value;
+
+ if (radial.style && radial.style.type === "position") {
+ const xDist = radial.style.value.x;
+ const yDist = radial.style.value.y;
+ const rx = xDist
+ ? resolveEllipseDimension({ distance: xDist, axisSize: width })
+ : width / 2;
+ const ry = yDist
+ ? resolveEllipseDimension({ distance: yDist, axisSize: height })
+ : height / 2;
+ return { cx, cy, rx, ry };
+ }
+
+ if (radial.style && radial.style.type !== "extent-keyword") {
+ const resolvedRadius = resolveDistanceInPixels({
+ distance: radial.style,
+ axisSize: Math.max(width, height),
+ });
+ if (shape === "circle") {
+ return {
+ cx,
+ cy,
+ rx: resolvedRadius ?? 0,
+ ry: resolvedRadius ?? 0,
+ };
+ }
+
+ const radius = resolvedRadius ?? 0;
+ return { cx, cy, rx: radius, ry: radius };
+ }
+
+ const extent =
+ radial.style && radial.style.type === "extent-keyword"
+ ? radial.style.value
+ : "farthest-corner";
+ const { rx, ry } = resolveRadialExtents({
+ width,
+ height,
+ cx,
+ cy,
+ shape,
+ extent,
});
return { cx, cy, rx, ry };
-};
+ }
-const resolveRadialCenter = ({
+ if (radial.type === "extent-keyword") {
+ const { cx, cy } = resolveRadialCenter({
+ width,
+ height,
+ position: radial.at,
+ });
+ const { rx, ry } = resolveRadialExtents({
+ width,
+ height,
+ cx,
+ cy,
+ shape: "ellipse",
+ extent: radial.value,
+ });
+ return { cx, cy, rx, ry };
+ }
+
+ const { cx, cy } = resolveRadialCenter({
width,
height,
- position,
-}: {
- width: number;
- height: number;
- position?: Position;
-}): { cx: number; cy: number } => {
- if (!position) {
- return { cx: width / 2, cy: height / 2 };
- }
-
- const normalized = normalizePositionKeywords({ position });
- const cx = resolvePositionValue({
- distance: normalized.value.x,
- axisSize: width,
- axis: "x",
- });
- const cy = resolvePositionValue({
- distance: normalized.value.y,
- axisSize: height,
- axis: "y",
- });
-
- return { cx, cy };
-};
-
-const normalizePositionKeywords = ({
- position,
-}: {
- position: Position;
-}): Position => {
- const xValue = position.value.x;
- const yValue = position.value.y;
-
- if (
- xValue?.type === "position-keyword" &&
- yValue?.type === "position-keyword"
- ) {
- const xKeyword = xValue.value.toLowerCase() as PositionKeyword;
- const yKeyword = yValue.value.toLowerCase() as PositionKeyword;
- const xIsVertical = xKeyword === "top" || xKeyword === "bottom";
- const yIsHorizontal = yKeyword === "left" || yKeyword === "right";
-
- if (xIsVertical && yIsHorizontal) {
- return {
- type: "position",
- value: {
- x: { type: "position-keyword", value: yKeyword },
- y: { type: "position-keyword", value: xKeyword },
- },
- };
- }
- }
-
- return position;
-};
-
-const resolveRadialExtents = ({
+ position: radial.at,
+ });
+ const { rx, ry } = resolveRadialExtents({
width,
height,
cx,
cy,
- shape,
- extent,
+ shape: "ellipse",
+ extent: "farthest-corner",
+ });
+ return { cx, cy, rx, ry };
+};
+
+const resolveRadialCenter = ({
+ width,
+ height,
+ position,
}: {
- width: number;
- height: number;
- cx: number;
- cy: number;
- shape: "circle" | "ellipse";
- extent: string;
-}): { rx: number; ry: number } => {
- const left = cx;
- const right = width - cx;
- const top = cy;
- const bottom = height - cy;
+ width: number;
+ height: number;
+ position?: Position;
+}): { cx: number; cy: number } => {
+ if (!position) {
+ return { cx: width / 2, cy: height / 2 };
+ }
- if (shape === "circle") {
- const distances = [
- Math.hypot(left, top),
- Math.hypot(right, top),
- Math.hypot(left, bottom),
- Math.hypot(right, bottom),
- ];
- if (extent === "closest-side") {
- return { rx: Math.min(left, right, top, bottom), ry: 0 };
- }
- if (extent === "farthest-side") {
- return { rx: Math.max(left, right, top, bottom), ry: 0 };
- }
- if (extent === "closest-corner") {
- return { rx: Math.min(...distances), ry: 0 };
- }
- return { rx: Math.max(...distances), ry: 0 };
+ const normalized = normalizePositionKeywords({ position });
+ const cx = resolvePositionValue({
+ distance: normalized.value.x,
+ axisSize: width,
+ axis: "x",
+ });
+ const cy = resolvePositionValue({
+ distance: normalized.value.y,
+ axisSize: height,
+ axis: "y",
+ });
+
+ return { cx, cy };
+};
+
+const normalizePositionKeywords = ({
+ position,
+}: {
+ position: Position;
+}): Position => {
+ const xValue = position.value.x;
+ const yValue = position.value.y;
+
+ if (
+ xValue?.type === "position-keyword" &&
+ yValue?.type === "position-keyword"
+ ) {
+ const xKeyword = xValue.value.toLowerCase() as PositionKeyword;
+ const yKeyword = yValue.value.toLowerCase() as PositionKeyword;
+ const xIsVertical = xKeyword === "top" || xKeyword === "bottom";
+ const yIsHorizontal = yKeyword === "left" || yKeyword === "right";
+
+ if (xIsVertical && yIsHorizontal) {
+ return {
+ type: "position",
+ value: {
+ x: { type: "position-keyword", value: yKeyword },
+ y: { type: "position-keyword", value: xKeyword },
+ },
+ };
}
+ }
+ return position;
+};
+
+const resolveRadialExtents = ({
+ width,
+ height,
+ cx,
+ cy,
+ shape,
+ extent,
+}: {
+ width: number;
+ height: number;
+ cx: number;
+ cy: number;
+ shape: "circle" | "ellipse";
+ extent: string;
+}): { rx: number; ry: number } => {
+ const left = cx;
+ const right = width - cx;
+ const top = cy;
+ const bottom = height - cy;
+
+ if (shape === "circle") {
+ const distances = [
+ Math.hypot(left, top),
+ Math.hypot(right, top),
+ Math.hypot(left, bottom),
+ Math.hypot(right, bottom),
+ ];
if (extent === "closest-side") {
- return { rx: Math.min(left, right), ry: Math.min(top, bottom) };
+ return { rx: Math.min(left, right, top, bottom), ry: 0 };
}
if (extent === "farthest-side") {
- return { rx: Math.max(left, right), ry: Math.max(top, bottom) };
+ return { rx: Math.max(left, right, top, bottom), ry: 0 };
}
+ if (extent === "closest-corner") {
+ return { rx: Math.min(...distances), ry: 0 };
+ }
+ return { rx: Math.max(...distances), ry: 0 };
+ }
- const corners = [
- { dx: left, dy: top },
- { dx: right, dy: top },
- { dx: left, dy: bottom },
- { dx: right, dy: bottom },
- ];
- const sorted = corners
- .slice()
- .sort((a, b) => Math.hypot(a.dx, a.dy) - Math.hypot(b.dx, b.dy));
- const chosen =
- extent === "closest-corner" ? sorted[0] : sorted[sorted.length - 1];
- return { rx: Math.abs(chosen.dx), ry: Math.abs(chosen.dy) };
+ if (extent === "closest-side") {
+ return { rx: Math.min(left, right), ry: Math.min(top, bottom) };
+ }
+ if (extent === "farthest-side") {
+ return { rx: Math.max(left, right), ry: Math.max(top, bottom) };
+ }
+
+ const corners = [
+ { dx: left, dy: top },
+ { dx: right, dy: top },
+ { dx: left, dy: bottom },
+ { dx: right, dy: bottom },
+ ];
+ const sorted = corners
+ .slice()
+ .sort((a, b) => Math.hypot(a.dx, a.dy) - Math.hypot(b.dx, b.dy));
+ const chosen =
+ extent === "closest-corner" ? sorted[0] : sorted[sorted.length - 1];
+ return { rx: Math.abs(chosen.dx), ry: Math.abs(chosen.dy) };
};
const resolvePositionValue = ({
- distance,
- axisSize,
- axis,
+ distance,
+ axisSize,
+ axis,
}: {
- distance?: Distance;
- axisSize: number;
- axis: "x" | "y";
+ distance?: Distance;
+ axisSize: number;
+ axis: "x" | "y";
}): number => {
- if (!distance) {
- return axisSize / 2;
- }
-
- if (distance.type === "%") {
- return (Number.parseFloat(distance.value) / 100) * axisSize;
- }
-
- if (distance.type === "position-keyword") {
- return keywordToPosition({
- value: distance.value,
- axisSize,
- axis,
- });
- }
-
- if (distance.type === "px") {
- return Number.parseFloat(distance.value);
- }
-
- if (distance.type === "em") {
- return Number.parseFloat(distance.value) * 16;
- }
-
+ if (!distance) {
return axisSize / 2;
+ }
+
+ if (distance.type === "%") {
+ return (Number.parseFloat(distance.value) / 100) * axisSize;
+ }
+
+ if (distance.type === "position-keyword") {
+ return keywordToPosition({
+ value: distance.value,
+ axisSize,
+ axis,
+ });
+ }
+
+ if (distance.type === "px") {
+ return Number.parseFloat(distance.value);
+ }
+
+ if (distance.type === "em") {
+ return Number.parseFloat(distance.value) * 16;
+ }
+
+ return axisSize / 2;
};
const resolveDistanceInPixels = ({
- distance,
- axisSize,
+ distance,
+ axisSize,
}: {
- distance: Distance;
- axisSize: number;
+ distance: Distance;
+ axisSize: number;
}): number | null => {
- if (distance.type === "%") {
- return (Number.parseFloat(distance.value) / 100) * axisSize;
- }
+ if (distance.type === "%") {
+ return (Number.parseFloat(distance.value) / 100) * axisSize;
+ }
- if (distance.type === "px") {
- return Number.parseFloat(distance.value);
- }
+ if (distance.type === "px") {
+ return Number.parseFloat(distance.value);
+ }
- if (distance.type === "em") {
- return Number.parseFloat(distance.value) * 16;
- }
+ if (distance.type === "em") {
+ return Number.parseFloat(distance.value) * 16;
+ }
- return null;
+ return null;
};
const resolveEllipseDimension = ({
- distance,
- axisSize,
+ distance,
+ axisSize,
}: {
- distance: Distance;
- axisSize: number;
+ distance: Distance;
+ axisSize: number;
}): number => {
- if (distance.type === "%") {
- return (Number.parseFloat(distance.value) / 100) * axisSize;
- }
+ if (distance.type === "%") {
+ return (Number.parseFloat(distance.value) / 100) * axisSize;
+ }
- if (distance.type === "px") {
- return Number.parseFloat(distance.value);
- }
+ if (distance.type === "px") {
+ return Number.parseFloat(distance.value);
+ }
- if (distance.type === "em") {
- return Number.parseFloat(distance.value) * 16;
- }
+ if (distance.type === "em") {
+ return Number.parseFloat(distance.value) * 16;
+ }
- return axisSize / 2;
+ return axisSize / 2;
};
const keywordToPosition = ({
- value,
- axisSize,
- axis,
+ value,
+ axisSize,
+ axis,
}: {
- value: string;
- axisSize: number;
- axis: "x" | "y";
+ value: string;
+ axisSize: number;
+ axis: "x" | "y";
}): number => {
- const keyword = value.toLowerCase() as PositionKeyword;
-
- if (keyword === "center") {
- return axisSize / 2;
- }
-
- if (axis === "x") {
- if (keyword === "left") {
- return 0;
- }
- if (keyword === "right") {
- return axisSize;
- }
- }
-
- if (axis === "y") {
- if (keyword === "top") {
- return 0;
- }
- if (keyword === "bottom") {
- return axisSize;
- }
- }
+ const keyword = value.toLowerCase() as PositionKeyword;
+ if (keyword === "center") {
return axisSize / 2;
+ }
+
+ if (axis === "x") {
+ if (keyword === "left") {
+ return 0;
+ }
+ if (keyword === "right") {
+ return axisSize;
+ }
+ }
+
+ if (axis === "y") {
+ if (keyword === "top") {
+ return 0;
+ }
+ if (keyword === "bottom") {
+ return axisSize;
+ }
+ }
+
+ return axisSize / 2;
};
const isTransparent = ({ color }: { color: string }): boolean => {
- const lower = color.toLowerCase().trim();
- if (lower === "transparent") {
- return true;
- }
- const rgbaMatch = lower.match(/^rgba\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*,\s*([\d.]+)\s*\)$/);
- if (rgbaMatch && Number.parseFloat(rgbaMatch[1]) === 0) {
- return true;
- }
- return false;
+ const lower = color.toLowerCase().trim();
+ if (lower === "transparent") {
+ return true;
+ }
+ const rgbaMatch = lower.match(
+ /^rgba\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*,\s*([\d.]+)\s*\)$/,
+ );
+ if (rgbaMatch && Number.parseFloat(rgbaMatch[1]) === 0) {
+ return true;
+ }
+ return false;
};
-const parseColorToRgb = ({ color }: { color: string }): { r: number; g: number; b: number; a: number } | null => {
- const lower = color.toLowerCase().trim();
+const parseColorToRgb = ({
+ color,
+}: {
+ color: string;
+}): { r: number; g: number; b: number; a: number } | null => {
+ const lower = color.toLowerCase().trim();
- const hexMatch = lower.match(/^#([0-9a-f]{3,8})$/);
- if (hexMatch) {
- const hex = hexMatch[1];
- if (hex.length === 3) {
- return {
- r: Number.parseInt(hex[0] + hex[0], 16),
- g: Number.parseInt(hex[1] + hex[1], 16),
- b: Number.parseInt(hex[2] + hex[2], 16),
- a: 1,
- };
- }
- if (hex.length === 6) {
- return {
- r: Number.parseInt(hex.slice(0, 2), 16),
- g: Number.parseInt(hex.slice(2, 4), 16),
- b: Number.parseInt(hex.slice(4, 6), 16),
- a: 1,
- };
- }
- if (hex.length === 8) {
- return {
- r: Number.parseInt(hex.slice(0, 2), 16),
- g: Number.parseInt(hex.slice(2, 4), 16),
- b: Number.parseInt(hex.slice(4, 6), 16),
- a: Number.parseInt(hex.slice(6, 8), 16) / 255,
- };
- }
+ const hexMatch = lower.match(/^#([0-9a-f]{3,8})$/);
+ if (hexMatch) {
+ const hex = hexMatch[1];
+ if (hex.length === 3) {
+ return {
+ r: Number.parseInt(hex[0] + hex[0], 16),
+ g: Number.parseInt(hex[1] + hex[1], 16),
+ b: Number.parseInt(hex[2] + hex[2], 16),
+ a: 1,
+ };
}
-
- const rgbMatch = lower.match(/^rgb\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/);
- if (rgbMatch) {
- return {
- r: Number.parseFloat(rgbMatch[1]),
- g: Number.parseFloat(rgbMatch[2]),
- b: Number.parseFloat(rgbMatch[3]),
- a: 1,
- };
+ if (hex.length === 6) {
+ return {
+ r: Number.parseInt(hex.slice(0, 2), 16),
+ g: Number.parseInt(hex.slice(2, 4), 16),
+ b: Number.parseInt(hex.slice(4, 6), 16),
+ a: 1,
+ };
}
-
- const rgbaMatch = lower.match(/^rgba\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/);
- if (rgbaMatch) {
- return {
- r: Number.parseFloat(rgbaMatch[1]),
- g: Number.parseFloat(rgbaMatch[2]),
- b: Number.parseFloat(rgbaMatch[3]),
- a: Number.parseFloat(rgbaMatch[4]),
- };
+ if (hex.length === 8) {
+ return {
+ r: Number.parseInt(hex.slice(0, 2), 16),
+ g: Number.parseInt(hex.slice(2, 4), 16),
+ b: Number.parseInt(hex.slice(4, 6), 16),
+ a: Number.parseInt(hex.slice(6, 8), 16) / 255,
+ };
}
+ }
- return null;
+ const rgbMatch = lower.match(
+ /^rgb\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/,
+ );
+ if (rgbMatch) {
+ return {
+ r: Number.parseFloat(rgbMatch[1]),
+ g: Number.parseFloat(rgbMatch[2]),
+ b: Number.parseFloat(rgbMatch[3]),
+ a: 1,
+ };
+ }
+
+ const rgbaMatch = lower.match(
+ /^rgba\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/,
+ );
+ if (rgbaMatch) {
+ return {
+ r: Number.parseFloat(rgbaMatch[1]),
+ g: Number.parseFloat(rgbaMatch[2]),
+ b: Number.parseFloat(rgbaMatch[3]),
+ a: Number.parseFloat(rgbaMatch[4]),
+ };
+ }
+
+ return null;
};
const fixTransparentStops = ({
- stops,
+ stops,
}: {
- stops: Array<{ color: string; offset: number | null }>;
+ stops: Array<{ color: string; offset: number | null }>;
}): Array<{ color: string; offset: number | null }> => {
- if (stops.length === 0) {
- return stops;
+ if (stops.length === 0) {
+ return stops;
+ }
+
+ const result = stops.map((stop) => ({ ...stop }));
+
+ for (let i = 0; i < result.length; i++) {
+ if (!isTransparent({ color: result[i].color })) {
+ continue;
}
- const result = stops.map((stop) => ({ ...stop }));
+ let donorColor: { r: number; g: number; b: number } | null = null;
- for (let i = 0; i < result.length; i++) {
- if (!isTransparent({ color: result[i].color })) {
- continue;
- }
-
- let donorColor: { r: number; g: number; b: number } | null = null;
-
- for (let j = i - 1; j >= 0; j--) {
- if (!isTransparent({ color: result[j].color })) {
- const parsed = parseColorToRgb({ color: result[j].color });
- if (parsed) {
- donorColor = parsed;
- break;
- }
- }
- }
-
- if (!donorColor) {
- for (let j = i + 1; j < result.length; j++) {
- if (!isTransparent({ color: result[j].color })) {
- const parsed = parseColorToRgb({ color: result[j].color });
- if (parsed) {
- donorColor = parsed;
- break;
- }
- }
- }
- }
-
- if (donorColor) {
- result[i].color = `rgba(${donorColor.r},${donorColor.g},${donorColor.b},0)`;
+ for (let j = i - 1; j >= 0; j--) {
+ if (!isTransparent({ color: result[j].color })) {
+ const parsed = parseColorToRgb({ color: result[j].color });
+ if (parsed) {
+ donorColor = parsed;
+ break;
}
+ }
}
- return result;
+ if (!donorColor) {
+ for (let j = i + 1; j < result.length; j++) {
+ if (!isTransparent({ color: result[j].color })) {
+ const parsed = parseColorToRgb({ color: result[j].color });
+ if (parsed) {
+ donorColor = parsed;
+ break;
+ }
+ }
+ }
+ }
+
+ if (donorColor) {
+ result[i].color =
+ `rgba(${donorColor.r},${donorColor.g},${donorColor.b},0)`;
+ }
+ }
+
+ return result;
};
const normalizeColorStops = ({
- colorStops,
- gradientLength,
+ colorStops,
+ gradientLength,
}: {
- colorStops: Array;
- gradientLength: number;
+ colorStops: Array;
+ gradientLength: number;
}): Array<{ color: string; offset: number }> => {
- const mappedStops = colorStops.map((stop) => ({
- color: colorToString({ color: stop }),
- offset: resolveStopOffset({ stop, gradientLength }),
- }));
+ const mappedStops = colorStops.map((stop) => ({
+ color: colorToString({ color: stop }),
+ offset: resolveStopOffset({ stop, gradientLength }),
+ }));
- const fixedStops = fixTransparentStops({ stops: mappedStops });
- const resolvedStops = fixedStops.map((stop) => ({ ...stop }));
- const knownIndices = resolvedStops
- .map((stop, index) => (stop.offset === null ? null : index))
- .filter((index): index is number => index !== null);
+ const fixedStops = fixTransparentStops({ stops: mappedStops });
+ const resolvedStops = fixedStops.map((stop) => ({ ...stop }));
+ const knownIndices = resolvedStops
+ .map((stop, index) => (stop.offset === null ? null : index))
+ .filter((index): index is number => index !== null);
- if (knownIndices.length === 0) {
- const step = resolvedStops.length > 1 ? 1 / (resolvedStops.length - 1) : 1;
- for (let index = 0; index < resolvedStops.length; index += 1) {
- resolvedStops[index].offset = step * index;
- }
- return clampStops({ stops: resolvedStops });
+ if (knownIndices.length === 0) {
+ const step = resolvedStops.length > 1 ? 1 / (resolvedStops.length - 1) : 1;
+ for (let index = 0; index < resolvedStops.length; index += 1) {
+ resolvedStops[index].offset = step * index;
}
-
- const firstKnown = knownIndices[0];
- if (resolvedStops[firstKnown].offset === null) {
- resolvedStops[firstKnown].offset = 0;
- }
-
- for (let index = 0; index < firstKnown; index += 1) {
- const nextOffset = resolvedStops[firstKnown].offset ?? 0;
- resolvedStops[index].offset = (nextOffset * index) / firstKnown;
- }
-
- for (let i = 0; i < knownIndices.length - 1; i += 1) {
- const startIndex = knownIndices[i];
- const endIndex = knownIndices[i + 1];
- const startOffset = resolvedStops[startIndex].offset ?? 0;
- const endOffset = resolvedStops[endIndex].offset ?? startOffset;
- const gap = endIndex - startIndex;
-
- if (gap <= 1) {
- continue;
- }
-
- const step = (endOffset - startOffset) / gap;
- for (let index = 1; index < gap; index += 1) {
- resolvedStops[startIndex + index].offset =
- startOffset + step * index;
- }
- }
-
- const lastKnown = knownIndices[knownIndices.length - 1];
- const lastOffset = resolvedStops[lastKnown].offset ?? 1;
- if (lastKnown < resolvedStops.length - 1) {
- const gap = resolvedStops.length - 1 - lastKnown;
- const step = (1 - lastOffset) / gap;
- for (let index = 1; index <= gap; index += 1) {
- resolvedStops[lastKnown + index].offset = lastOffset + step * index;
- }
- }
-
return clampStops({ stops: resolvedStops });
+ }
+
+ const firstKnown = knownIndices[0];
+ if (resolvedStops[firstKnown].offset === null) {
+ resolvedStops[firstKnown].offset = 0;
+ }
+
+ for (let index = 0; index < firstKnown; index += 1) {
+ const nextOffset = resolvedStops[firstKnown].offset ?? 0;
+ resolvedStops[index].offset = (nextOffset * index) / firstKnown;
+ }
+
+ for (let i = 0; i < knownIndices.length - 1; i += 1) {
+ const startIndex = knownIndices[i];
+ const endIndex = knownIndices[i + 1];
+ const startOffset = resolvedStops[startIndex].offset ?? 0;
+ const endOffset = resolvedStops[endIndex].offset ?? startOffset;
+ const gap = endIndex - startIndex;
+
+ if (gap <= 1) {
+ continue;
+ }
+
+ const step = (endOffset - startOffset) / gap;
+ for (let index = 1; index < gap; index += 1) {
+ resolvedStops[startIndex + index].offset = startOffset + step * index;
+ }
+ }
+
+ const lastKnown = knownIndices[knownIndices.length - 1];
+ const lastOffset = resolvedStops[lastKnown].offset ?? 1;
+ if (lastKnown < resolvedStops.length - 1) {
+ const gap = resolvedStops.length - 1 - lastKnown;
+ const step = (1 - lastOffset) / gap;
+ for (let index = 1; index <= gap; index += 1) {
+ resolvedStops[lastKnown + index].offset = lastOffset + step * index;
+ }
+ }
+
+ return clampStops({ stops: resolvedStops });
};
const clampStops = ({
- stops,
+ stops,
}: {
- stops: Array<{ color: string; offset: number | null }>;
+ stops: Array<{ color: string; offset: number | null }>;
}): Array<{ color: string; offset: number }> => {
- return stops.map((stop) => ({
- color: stop.color,
- offset: clamp01({ value: stop.offset ?? 0 }),
- }));
+ return stops.map((stop) => ({
+ color: stop.color,
+ offset: clamp01({ value: stop.offset ?? 0 }),
+ }));
};
const resolveStopOffset = ({
- stop,
- gradientLength,
+ stop,
+ gradientLength,
}: {
- stop: ColorStop;
- gradientLength: number;
+ stop: ColorStop;
+ gradientLength: number;
}): number | null => {
- if (!stop.length) {
- return null;
- }
-
- if (stop.length.type === "%") {
- return Number.parseFloat(stop.length.value) / 100;
- }
-
- if (stop.length.type === "px") {
- return Number.parseFloat(stop.length.value) / gradientLength;
- }
-
- if (stop.length.type === "em") {
- return (Number.parseFloat(stop.length.value) * 16) / gradientLength;
- }
-
+ if (!stop.length) {
return null;
+ }
+
+ if (stop.length.type === "%") {
+ return Number.parseFloat(stop.length.value) / 100;
+ }
+
+ if (stop.length.type === "px") {
+ return Number.parseFloat(stop.length.value) / gradientLength;
+ }
+
+ if (stop.length.type === "em") {
+ return (Number.parseFloat(stop.length.value) * 16) / gradientLength;
+ }
+
+ return null;
};
const clamp01 = ({ value }: { value: number }): number => {
- if (value < 0) {
- return 0;
- }
- if (value > 1) {
- return 1;
- }
- return value;
+ if (value < 0) {
+ return 0;
+ }
+ if (value > 1) {
+ return 1;
+ }
+ return value;
};
const colorToString = ({ color }: { color: Color }): string => {
- if (color.type === "hex") {
- return `#${color.value}`;
- }
+ if (color.type === "hex") {
+ return `#${color.value}`;
+ }
- if (color.type === "literal") {
- return color.value;
- }
+ if (color.type === "literal") {
+ return color.value;
+ }
- if (color.type === "rgb") {
- return `rgb(${color.value.join(",")})`;
- }
+ if (color.type === "rgb") {
+ return `rgb(${color.value.join(",")})`;
+ }
- if (color.type === "rgba") {
- return `rgba(${color.value.join(",")})`;
- }
+ if (color.type === "rgba") {
+ return `rgba(${color.value.join(",")})`;
+ }
- if (color.type === "hsl") {
- return `hsl(${color.value.join(",")})`;
- }
+ if (color.type === "hsl") {
+ return `hsl(${color.value.join(",")})`;
+ }
- if (color.type === "hsla") {
- return `hsla(${color.value.join(",")})`;
- }
+ if (color.type === "hsla") {
+ return `hsla(${color.value.join(",")})`;
+ }
- return `var(${color.value})`;
+ return `var(${color.value})`;
};
diff --git a/apps/web/src/lib/gradients/index.ts b/apps/web/src/lib/gradients/index.ts
index 642b30da..490a8fbf 100644
--- a/apps/web/src/lib/gradients/index.ts
+++ b/apps/web/src/lib/gradients/index.ts
@@ -1,2 +1,2 @@
export * from "./parser";
-export * from "./canvas";
\ No newline at end of file
+export * from "./canvas";
diff --git a/apps/web/src/lib/gradients/parser.ts b/apps/web/src/lib/gradients/parser.ts
index ecc2b2e8..38f54723 100644
--- a/apps/web/src/lib/gradients/parser.ts
+++ b/apps/web/src/lib/gradients/parser.ts
@@ -1,23 +1,23 @@
/*
* Original source: https://github.com/rafaelcaricio/gradient-parser/blob/master/lib/parser.js
-*/
+ */
type GradientType =
- | "linear-gradient"
- | "repeating-linear-gradient"
- | "radial-gradient"
- | "repeating-radial-gradient";
+ | "linear-gradient"
+ | "repeating-linear-gradient"
+ | "radial-gradient"
+ | "repeating-radial-gradient";
type DirectionalOrientation = { type: "directional"; value: string };
type AngularOrientation = { type: "angular"; value: string };
type LinearOrientation = DirectionalOrientation | AngularOrientation;
type Distance =
- | { type: "%"; value: string }
- | { type: "position-keyword"; value: string }
- | { type: "calc"; value: string }
- | { type: "px"; value: string }
- | { type: "em"; value: string };
+ | { type: "%"; value: string }
+ | { type: "position-keyword"; value: string }
+ | { type: "calc"; value: string }
+ | { type: "px"; value: string }
+ | { type: "em"; value: string };
type PositionValue = { x?: Distance; y?: Distance };
type Position = { type: "position"; value: PositionValue };
@@ -26,616 +26,627 @@ type ExtentKeyword = { type: "extent-keyword"; value: string };
type ShapeValue = "circle" | "ellipse";
type Shape = {
- type: "shape";
- value: ShapeValue;
- style?: Distance | ExtentKeyword | Position;
- at?: Position;
+ type: "shape";
+ value: ShapeValue;
+ style?: Distance | ExtentKeyword | Position;
+ at?: Position;
};
type DefaultRadial = { type: "default-radial"; at: Position };
-type RadialOrientation = Shape | (ExtentKeyword & { at?: Position }) | DefaultRadial;
+type RadialOrientation =
+ | Shape
+ | (ExtentKeyword & { at?: Position })
+ | DefaultRadial;
export type GradientOrientation = LinearOrientation | Array;
export type Color =
- | { type: "hex"; value: string }
- | { type: "literal"; value: string }
- | { type: "rgb"; value: Array }
- | { type: "rgba"; value: Array }
- | { type: "hsl"; value: [string, string, string] }
- | { type: "hsla"; value: [string, string, string, string] }
- | { type: "var"; value: string };
+ | { type: "hex"; value: string }
+ | { type: "literal"; value: string }
+ | { type: "rgb"; value: Array }
+ | { type: "rgba"; value: Array }
+ | { type: "hsl"; value: [string, string, string] }
+ | { type: "hsla"; value: [string, string, string, string] }
+ | { type: "var"; value: string };
export type ColorStop = Color & { length?: Distance };
export type GradientAst = {
- type: GradientType;
- orientation: GradientOrientation | undefined;
- colorStops: Array;
+ type: GradientType;
+ orientation: GradientOrientation | undefined;
+ colorStops: Array;
};
type Tokens = {
- linearGradient: RegExp;
- repeatingLinearGradient: RegExp;
- radialGradient: RegExp;
- repeatingRadialGradient: RegExp;
- sideOrCorner: RegExp;
- extentKeywords: RegExp;
- positionKeywords: RegExp;
- pixelValue: RegExp;
- percentageValue: RegExp;
- emValue: RegExp;
- angleValue: RegExp;
- radianValue: RegExp;
- startCall: RegExp;
- endCall: RegExp;
- comma: RegExp;
- hexColor: RegExp;
- literalColor: RegExp;
- rgbColor: RegExp;
- rgbaColor: RegExp;
- varColor: RegExp;
- calcValue: RegExp;
- variableName: RegExp;
- number: RegExp;
- hslColor: RegExp;
- hslaColor: RegExp;
+ linearGradient: RegExp;
+ repeatingLinearGradient: RegExp;
+ radialGradient: RegExp;
+ repeatingRadialGradient: RegExp;
+ sideOrCorner: RegExp;
+ extentKeywords: RegExp;
+ positionKeywords: RegExp;
+ pixelValue: RegExp;
+ percentageValue: RegExp;
+ emValue: RegExp;
+ angleValue: RegExp;
+ radianValue: RegExp;
+ startCall: RegExp;
+ endCall: RegExp;
+ comma: RegExp;
+ hexColor: RegExp;
+ literalColor: RegExp;
+ rgbColor: RegExp;
+ rgbaColor: RegExp;
+ varColor: RegExp;
+ calcValue: RegExp;
+ variableName: RegExp;
+ number: RegExp;
+ hslColor: RegExp;
+ hslaColor: RegExp;
};
const tokens: Tokens = {
- linearGradient: /^(\-(webkit|o|ms|moz)\-)?(linear\-gradient)/i,
- repeatingLinearGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-linear\-gradient)/i,
- radialGradient: /^(\-(webkit|o|ms|moz)\-)?(radial\-gradient)/i,
- repeatingRadialGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i,
- sideOrCorner:
- /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i,
- extentKeywords:
- /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/,
- positionKeywords: /^(left|center|right|top|bottom)/i,
- pixelValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))px/,
- percentageValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))\%/,
- emValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))em/,
- angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/,
- radianValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))rad/,
- startCall: /^\(/,
- endCall: /^\)/,
- comma: /^,/,
- hexColor: /^\#([0-9a-fA-F]+)/,
- literalColor: /^([a-zA-Z]+)/,
- rgbColor: /^rgb/i,
- rgbaColor: /^rgba/i,
- varColor: /^var/i,
- calcValue: /^calc/i,
- variableName: /^(--[a-zA-Z0-9-,\s\#]+)/,
- number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/,
- hslColor: /^hsl/i,
- hslaColor: /^hsla/i,
+ linearGradient: /^(\-(webkit|o|ms|moz)\-)?(linear\-gradient)/i,
+ repeatingLinearGradient:
+ /^(\-(webkit|o|ms|moz)\-)?(repeating\-linear\-gradient)/i,
+ radialGradient: /^(\-(webkit|o|ms|moz)\-)?(radial\-gradient)/i,
+ repeatingRadialGradient:
+ /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i,
+ sideOrCorner:
+ /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i,
+ extentKeywords:
+ /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/,
+ positionKeywords: /^(left|center|right|top|bottom)/i,
+ pixelValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))px/,
+ percentageValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))\%/,
+ emValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))em/,
+ angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/,
+ radianValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))rad/,
+ startCall: /^\(/,
+ endCall: /^\)/,
+ comma: /^,/,
+ hexColor: /^\#([0-9a-fA-F]+)/,
+ literalColor: /^([a-zA-Z]+)/,
+ rgbColor: /^rgb/i,
+ rgbaColor: /^rgba/i,
+ varColor: /^var/i,
+ calcValue: /^calc/i,
+ variableName: /^(--[a-zA-Z0-9-,\s\#]+)/,
+ number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/,
+ hslColor: /^hsl/i,
+ hslaColor: /^hsla/i,
};
let input = "";
const error = ({ message }: { message: string }): never => {
- const err = new Error(`${input}: ${message}`);
- (err as Error & { source?: string }).source = input;
- throw err;
+ const err = new Error(`${input}: ${message}`);
+ (err as Error & { source?: string }).source = input;
+ throw err;
};
const getAst = (): Array => {
- const ast = matchListDefinitions();
+ const ast = matchListDefinitions();
- if (input.length > 0) {
- error({ message: "Invalid input not EOF" });
- }
+ if (input.length > 0) {
+ error({ message: "Invalid input not EOF" });
+ }
- return ast;
+ return ast;
};
const matchListDefinitions = (): Array =>
- matchListing({ matcher: matchDefinition });
+ matchListing({ matcher: matchDefinition });
const matchDefinition = (): GradientAst | undefined =>
- matchGradient({
- gradientType: "linear-gradient",
- pattern: tokens.linearGradient,
- orientationMatcher: matchLinearOrientation,
- }) ||
- matchGradient({
- gradientType: "repeating-linear-gradient",
- pattern: tokens.repeatingLinearGradient,
- orientationMatcher: matchLinearOrientation,
- }) ||
- matchGradient({
- gradientType: "radial-gradient",
- pattern: tokens.radialGradient,
- orientationMatcher: matchListRadialOrientations,
- }) ||
- matchGradient({
- gradientType: "repeating-radial-gradient",
- pattern: tokens.repeatingRadialGradient,
- orientationMatcher: matchListRadialOrientations,
- });
+ matchGradient({
+ gradientType: "linear-gradient",
+ pattern: tokens.linearGradient,
+ orientationMatcher: matchLinearOrientation,
+ }) ||
+ matchGradient({
+ gradientType: "repeating-linear-gradient",
+ pattern: tokens.repeatingLinearGradient,
+ orientationMatcher: matchLinearOrientation,
+ }) ||
+ matchGradient({
+ gradientType: "radial-gradient",
+ pattern: tokens.radialGradient,
+ orientationMatcher: matchListRadialOrientations,
+ }) ||
+ matchGradient({
+ gradientType: "repeating-radial-gradient",
+ pattern: tokens.repeatingRadialGradient,
+ orientationMatcher: matchListRadialOrientations,
+ });
const matchGradient = ({
- gradientType,
- pattern,
- orientationMatcher,
+ gradientType,
+ pattern,
+ orientationMatcher,
}: {
- gradientType: GradientType;
- pattern: RegExp;
- orientationMatcher: () => GradientOrientation | undefined;
+ gradientType: GradientType;
+ pattern: RegExp;
+ orientationMatcher: () => GradientOrientation | undefined;
}): GradientAst | undefined =>
- matchCall({
- pattern,
- callback: () => {
- const orientation = orientationMatcher();
- if (orientation && !scan({ regexp: tokens.comma })) {
- error({ message: "Missing comma before color stops" });
- }
+ matchCall({
+ pattern,
+ callback: () => {
+ const orientation = orientationMatcher();
+ if (orientation && !scan({ regexp: tokens.comma })) {
+ error({ message: "Missing comma before color stops" });
+ }
- return {
- type: gradientType,
- orientation,
- colorStops: matchListing({ matcher: matchColorStop }),
- };
- },
- });
+ return {
+ type: gradientType,
+ orientation,
+ colorStops: matchListing({ matcher: matchColorStop }),
+ };
+ },
+ });
const matchCall = ({
- pattern,
- callback,
+ pattern,
+ callback,
}: {
- pattern: RegExp;
- callback: (captures: RegExpExecArray) => T;
+ pattern: RegExp;
+ callback: (captures: RegExpExecArray) => T;
}): T | undefined => {
- const captures = scan({ regexp: pattern });
+ const captures = scan({ regexp: pattern });
- if (!captures) {
- return undefined;
- }
+ if (!captures) {
+ return undefined;
+ }
- if (!scan({ regexp: tokens.startCall })) {
- error({ message: "Missing (" });
- }
+ if (!scan({ regexp: tokens.startCall })) {
+ error({ message: "Missing (" });
+ }
- const result = callback(captures);
+ const result = callback(captures);
- if (!scan({ regexp: tokens.endCall })) {
- error({ message: "Missing )" });
- }
+ if (!scan({ regexp: tokens.endCall })) {
+ error({ message: "Missing )" });
+ }
- return result;
+ return result;
};
const matchLinearOrientation = (): LinearOrientation | undefined => {
- const sideOrCorner = matchSideOrCorner();
- if (sideOrCorner) {
- return sideOrCorner;
- }
+ const sideOrCorner = matchSideOrCorner();
+ if (sideOrCorner) {
+ return sideOrCorner;
+ }
- const legacyDirection = match({
- type: "position-keyword",
- pattern: tokens.positionKeywords,
- captureIndex: 1,
- });
- if (legacyDirection) {
- return {
- type: "directional",
- value: legacyDirection.value,
- };
- }
+ const legacyDirection = match({
+ type: "position-keyword",
+ pattern: tokens.positionKeywords,
+ captureIndex: 1,
+ });
+ if (legacyDirection) {
+ return {
+ type: "directional",
+ value: legacyDirection.value,
+ };
+ }
- return matchAngle();
+ return matchAngle();
};
const matchSideOrCorner = (): DirectionalOrientation | undefined =>
- match({ type: "directional", pattern: tokens.sideOrCorner, captureIndex: 1 });
+ match({ type: "directional", pattern: tokens.sideOrCorner, captureIndex: 1 });
const matchAngle = (): AngularOrientation | undefined =>
- match({ type: "angular", pattern: tokens.angleValue, captureIndex: 1 }) ||
- match({ type: "angular", pattern: tokens.radianValue, captureIndex: 1 });
+ match({ type: "angular", pattern: tokens.angleValue, captureIndex: 1 }) ||
+ match({ type: "angular", pattern: tokens.radianValue, captureIndex: 1 });
-const matchListRadialOrientations = (): Array | undefined => {
- const radialOrientation = matchRadialOrientation();
- if (!radialOrientation) {
- return undefined;
- }
+const matchListRadialOrientations = ():
+ | Array
+ | undefined => {
+ const radialOrientation = matchRadialOrientation();
+ if (!radialOrientation) {
+ return undefined;
+ }
- const radialOrientations: Array = [radialOrientation];
- const lookaheadCache = input;
+ const radialOrientations: Array = [radialOrientation];
+ const lookaheadCache = input;
- if (!scan({ regexp: tokens.comma })) {
- return radialOrientations;
- }
-
- const nextRadial = matchRadialOrientation();
- if (!nextRadial) {
- input = lookaheadCache;
- return radialOrientations;
- }
-
- radialOrientations.push(nextRadial);
+ if (!scan({ regexp: tokens.comma })) {
return radialOrientations;
+ }
+
+ const nextRadial = matchRadialOrientation();
+ if (!nextRadial) {
+ input = lookaheadCache;
+ return radialOrientations;
+ }
+
+ radialOrientations.push(nextRadial);
+ return radialOrientations;
};
const matchRadialOrientation = (): RadialOrientation | undefined => {
- const radialType = matchCircle() || matchEllipse();
- if (radialType) {
- radialType.at = matchAtPosition();
- return radialType;
- }
+ const radialType = matchCircle() || matchEllipse();
+ if (radialType) {
+ radialType.at = matchAtPosition();
+ return radialType;
+ }
- const extent = matchExtentKeyword();
- if (extent) {
- const positionAt = matchAtPosition();
- if (positionAt) {
- return { ...extent, at: positionAt };
- }
- return extent;
+ const extent = matchExtentKeyword();
+ if (extent) {
+ const positionAt = matchAtPosition();
+ if (positionAt) {
+ return { ...extent, at: positionAt };
}
+ return extent;
+ }
- const implicitEllipse = matchImplicitEllipse();
- if (implicitEllipse) {
- return implicitEllipse;
- }
+ const implicitEllipse = matchImplicitEllipse();
+ if (implicitEllipse) {
+ return implicitEllipse;
+ }
- const atPosition = matchAtPosition();
- if (atPosition) {
- return { type: "default-radial", at: atPosition };
- }
+ const atPosition = matchAtPosition();
+ if (atPosition) {
+ return { type: "default-radial", at: atPosition };
+ }
- const defaultPosition = matchPositioning();
- if (defaultPosition) {
- return { type: "default-radial", at: defaultPosition };
- }
+ const defaultPosition = matchPositioning();
+ if (defaultPosition) {
+ return { type: "default-radial", at: defaultPosition };
+ }
- return undefined;
+ return undefined;
};
const matchImplicitEllipse = (): Shape | undefined => {
- const lookaheadCache = input;
+ const lookaheadCache = input;
- const width = matchDistance();
- if (!width) {
- return undefined;
- }
+ const width = matchDistance();
+ if (!width) {
+ return undefined;
+ }
- const height = matchDistance();
- if (!height) {
- input = lookaheadCache;
- return undefined;
- }
+ const height = matchDistance();
+ if (!height) {
+ input = lookaheadCache;
+ return undefined;
+ }
- const atPos = matchAtPosition();
- if (!atPos) {
- input = lookaheadCache;
- return undefined;
- }
+ const atPos = matchAtPosition();
+ if (!atPos) {
+ input = lookaheadCache;
+ return undefined;
+ }
- return {
- type: "shape",
- value: "ellipse",
- style: { type: "position", value: { x: width, y: height } },
- at: atPos,
- };
+ return {
+ type: "shape",
+ value: "ellipse",
+ style: { type: "position", value: { x: width, y: height } },
+ at: atPos,
+ };
};
const matchCircle = (): Shape | undefined => {
- const circle = match({
- type: "shape",
- pattern: /^(circle)/i,
- captureIndex: 0,
- }) as Shape | undefined;
+ const circle = match({
+ type: "shape",
+ pattern: /^(circle)/i,
+ captureIndex: 0,
+ }) as Shape | undefined;
- if (!circle) {
- return undefined;
- }
+ if (!circle) {
+ return undefined;
+ }
- circle.style = matchLength() || matchExtentKeyword();
- circle.value = "circle";
- return circle;
+ circle.style = matchLength() || matchExtentKeyword();
+ circle.value = "circle";
+ return circle;
};
const matchEllipse = (): Shape | undefined => {
- const ellipse = match({
- type: "shape",
- pattern: /^(ellipse)/i,
- captureIndex: 0,
- }) as Shape | undefined;
+ const ellipse = match({
+ type: "shape",
+ pattern: /^(ellipse)/i,
+ captureIndex: 0,
+ }) as Shape | undefined;
- if (!ellipse) {
- return undefined;
- }
+ if (!ellipse) {
+ return undefined;
+ }
- ellipse.style =
- matchPositioning() || matchDistance() || matchExtentKeyword();
- ellipse.value = "ellipse";
- return ellipse;
+ ellipse.style = matchPositioning() || matchDistance() || matchExtentKeyword();
+ ellipse.value = "ellipse";
+ return ellipse;
};
const matchExtentKeyword = (): ExtentKeyword | undefined =>
- match({
- type: "extent-keyword",
- pattern: tokens.extentKeywords,
- captureIndex: 1,
- });
+ match({
+ type: "extent-keyword",
+ pattern: tokens.extentKeywords,
+ captureIndex: 1,
+ });
const matchAtPosition = (): Position | undefined => {
- if (!match({ type: "position", pattern: /^at/, captureIndex: 0 })) {
- return undefined;
- }
+ if (!match({ type: "position", pattern: /^at/, captureIndex: 0 })) {
+ return undefined;
+ }
- const positioning = matchPositioning();
- if (!positioning) {
- error({ message: "Missing positioning value" });
- }
+ const positioning = matchPositioning();
+ if (!positioning) {
+ error({ message: "Missing positioning value" });
+ }
- return positioning;
+ return positioning;
};
const matchPositioning = (): Position | undefined => {
- const location = matchCoordinates();
+ const location = matchCoordinates();
- if (!location.x && !location.y) {
- return undefined;
- }
+ if (!location.x && !location.y) {
+ return undefined;
+ }
- return {
- type: "position",
- value: location,
- };
+ return {
+ type: "position",
+ value: location,
+ };
};
const matchCoordinates = (): PositionValue => ({
- x: matchDistance(),
- y: matchDistance(),
+ x: matchDistance(),
+ y: matchDistance(),
});
const matchListing = ({
- matcher,
+ matcher,
}: {
- matcher: () => T | undefined;
+ matcher: () => T | undefined;
}): Array => {
- const captures = matcher();
- const result: Array = [];
-
- if (!captures) {
- return result;
- }
-
- result.push(captures);
- while (scan({ regexp: tokens.comma })) {
- const nextCapture =
- matcher() ?? error({ message: "One extra comma" });
- result.push(nextCapture);
- }
+ const captures = matcher();
+ const result: Array = [];
+ if (!captures) {
return result;
+ }
+
+ result.push(captures);
+ while (scan({ regexp: tokens.comma })) {
+ const nextCapture = matcher() ?? error({ message: "One extra comma" });
+ result.push(nextCapture);
+ }
+
+ return result;
};
const matchColorStop = (): ColorStop => {
- const color = matchColor() ?? error({ message: "Expected color definition" });
- const length = matchDistance();
- return { ...color, length };
+ const color = matchColor() ?? error({ message: "Expected color definition" });
+ const length = matchDistance();
+ return { ...color, length };
};
const matchColor = (): Color | undefined =>
- matchHexColor() ||
- matchHSLAColor() ||
- matchHSLColor() ||
- matchRGBAColor() ||
- matchRGBColor() ||
- matchVarColor() ||
- matchLiteralColor();
+ matchHexColor() ||
+ matchHSLAColor() ||
+ matchHSLColor() ||
+ matchRGBAColor() ||
+ matchRGBColor() ||
+ matchVarColor() ||
+ matchLiteralColor();
const matchLiteralColor = (): Color | undefined =>
- match({ type: "literal", pattern: tokens.literalColor, captureIndex: 0 });
+ match({ type: "literal", pattern: tokens.literalColor, captureIndex: 0 });
const matchHexColor = (): Color | undefined =>
- match({ type: "hex", pattern: tokens.hexColor, captureIndex: 1 });
+ match({ type: "hex", pattern: tokens.hexColor, captureIndex: 1 });
const matchRGBColor = (): Color | undefined =>
- matchCall({
- pattern: tokens.rgbColor,
- callback: () => ({
- type: "rgb",
- value: matchListing({ matcher: matchNumber }),
- }),
- });
+ matchCall({
+ pattern: tokens.rgbColor,
+ callback: () => ({
+ type: "rgb",
+ value: matchListing({ matcher: matchNumber }),
+ }),
+ });
const matchRGBAColor = (): Color | undefined =>
- matchCall({
- pattern: tokens.rgbaColor,
- callback: () => ({
- type: "rgba",
- value: matchListing({ matcher: matchNumber }),
- }),
- });
+ matchCall({
+ pattern: tokens.rgbaColor,
+ callback: () => ({
+ type: "rgba",
+ value: matchListing({ matcher: matchNumber }),
+ }),
+ });
const matchVarColor = (): Color | undefined =>
- matchCall({
- pattern: tokens.varColor,
- callback: () => ({
- type: "var",
- value: matchVariableName(),
- }),
- });
+ matchCall({
+ pattern: tokens.varColor,
+ callback: () => ({
+ type: "var",
+ value: matchVariableName(),
+ }),
+ });
const matchHSLColor = (): Color | undefined =>
- matchCall({
- pattern: tokens.hslColor,
- callback: () => {
- const lookahead = scan({ regexp: tokens.percentageValue });
- if (lookahead) {
- error({
- message:
- "HSL hue value must be a number in degrees (0-360) or normalized (-360 to 360), not a percentage",
- });
- }
+ matchCall({
+ pattern: tokens.hslColor,
+ callback: () => {
+ const lookahead = scan({ regexp: tokens.percentageValue });
+ if (lookahead) {
+ error({
+ message:
+ "HSL hue value must be a number in degrees (0-360) or normalized (-360 to 360), not a percentage",
+ });
+ }
- const hue = matchNumber();
- scan({ regexp: tokens.comma });
- let captures = scan({ regexp: tokens.percentageValue });
- const sat = captures ? captures[1] : null;
- scan({ regexp: tokens.comma });
- captures = scan({ regexp: tokens.percentageValue });
- const light = captures ? captures[1] : null;
- const ensuredSat =
- sat ??
- error({
- message: "Expected percentage value for saturation and lightness in HSL",
- });
- const ensuredLight =
- light ??
- error({
- message: "Expected percentage value for saturation and lightness in HSL",
- });
- return {
- type: "hsl",
- value: [hue, ensuredSat, ensuredLight],
- };
- },
- });
+ const hue = matchNumber();
+ scan({ regexp: tokens.comma });
+ let captures = scan({ regexp: tokens.percentageValue });
+ const sat = captures ? captures[1] : null;
+ scan({ regexp: tokens.comma });
+ captures = scan({ regexp: tokens.percentageValue });
+ const light = captures ? captures[1] : null;
+ const ensuredSat =
+ sat ??
+ error({
+ message:
+ "Expected percentage value for saturation and lightness in HSL",
+ });
+ const ensuredLight =
+ light ??
+ error({
+ message:
+ "Expected percentage value for saturation and lightness in HSL",
+ });
+ return {
+ type: "hsl",
+ value: [hue, ensuredSat, ensuredLight],
+ };
+ },
+ });
const matchHSLAColor = (): Color | undefined =>
- matchCall({
- pattern: tokens.hslaColor,
- callback: () => {
- const hue = matchNumber();
- scan({ regexp: tokens.comma });
- let captures = scan({ regexp: tokens.percentageValue });
- const sat = captures ? captures[1] : null;
- scan({ regexp: tokens.comma });
- captures = scan({ regexp: tokens.percentageValue });
- const light = captures ? captures[1] : null;
- scan({ regexp: tokens.comma });
- const alpha = matchNumber();
- const ensuredSat =
- sat ??
- error({
- message:
- "Expected percentage value for saturation and lightness in HSLA",
- });
- const ensuredLight =
- light ??
- error({
- message:
- "Expected percentage value for saturation and lightness in HSLA",
- });
- return {
- type: "hsla",
- value: [hue, ensuredSat, ensuredLight, alpha],
- };
- },
- });
+ matchCall({
+ pattern: tokens.hslaColor,
+ callback: () => {
+ const hue = matchNumber();
+ scan({ regexp: tokens.comma });
+ let captures = scan({ regexp: tokens.percentageValue });
+ const sat = captures ? captures[1] : null;
+ scan({ regexp: tokens.comma });
+ captures = scan({ regexp: tokens.percentageValue });
+ const light = captures ? captures[1] : null;
+ scan({ regexp: tokens.comma });
+ const alpha = matchNumber();
+ const ensuredSat =
+ sat ??
+ error({
+ message:
+ "Expected percentage value for saturation and lightness in HSLA",
+ });
+ const ensuredLight =
+ light ??
+ error({
+ message:
+ "Expected percentage value for saturation and lightness in HSLA",
+ });
+ return {
+ type: "hsla",
+ value: [hue, ensuredSat, ensuredLight, alpha],
+ };
+ },
+ });
const matchVariableName = (): string => {
- const captures =
- scan({ regexp: tokens.variableName }) ??
- error({ message: "Expected CSS variable name" });
- return captures[1];
+ const captures =
+ scan({ regexp: tokens.variableName }) ??
+ error({ message: "Expected CSS variable name" });
+ return captures[1];
};
const matchNumber = (): string => {
- const captures =
- scan({ regexp: tokens.number }) ?? error({ message: "Expected number" });
- return captures[1];
+ const captures =
+ scan({ regexp: tokens.number }) ?? error({ message: "Expected number" });
+ return captures[1];
};
const matchDistance = (): Distance | undefined =>
- match({ type: "%", pattern: tokens.percentageValue, captureIndex: 1 }) ||
- matchPositionKeyword() ||
- matchCalc() ||
- matchLength();
+ match({ type: "%", pattern: tokens.percentageValue, captureIndex: 1 }) ||
+ matchPositionKeyword() ||
+ matchCalc() ||
+ matchLength();
const matchPositionKeyword = (): Distance | undefined =>
- match({
- type: "position-keyword",
- pattern: tokens.positionKeywords,
- captureIndex: 1,
- });
+ match({
+ type: "position-keyword",
+ pattern: tokens.positionKeywords,
+ captureIndex: 1,
+ });
const matchCalc = (): Distance | undefined =>
- matchCall({
- pattern: tokens.calcValue,
- callback: () => {
- let openParenCount = 1;
- let index = 0;
+ matchCall({
+ pattern: tokens.calcValue,
+ callback: () => {
+ let openParenCount = 1;
+ let index = 0;
- while (openParenCount > 0 && index < input.length) {
- const char = input.charAt(index);
- if (char === "(") {
- openParenCount++;
- } else if (char === ")") {
- openParenCount--;
- }
- index++;
- }
+ while (openParenCount > 0 && index < input.length) {
+ const char = input.charAt(index);
+ if (char === "(") {
+ openParenCount++;
+ } else if (char === ")") {
+ openParenCount--;
+ }
+ index++;
+ }
- if (openParenCount > 0) {
- error({ message: "Missing closing parenthesis in calc() expression" });
- }
+ if (openParenCount > 0) {
+ error({ message: "Missing closing parenthesis in calc() expression" });
+ }
- const calcContent = input.slice(0, index - 1);
- consume({ size: index - 1 });
+ const calcContent = input.slice(0, index - 1);
+ consume({ size: index - 1 });
- return {
- type: "calc",
- value: calcContent,
- };
- },
- });
+ return {
+ type: "calc",
+ value: calcContent,
+ };
+ },
+ });
const matchLength = (): Distance | undefined =>
- match({ type: "px", pattern: tokens.pixelValue, captureIndex: 1 }) ||
- match({ type: "em", pattern: tokens.emValue, captureIndex: 1 });
+ match({ type: "px", pattern: tokens.pixelValue, captureIndex: 1 }) ||
+ match({ type: "em", pattern: tokens.emValue, captureIndex: 1 });
const match = ({
- type,
- pattern,
- captureIndex,
+ type,
+ pattern,
+ captureIndex,
}: {
- type: TType;
- pattern: RegExp;
- captureIndex: number;
+ type: TType;
+ pattern: RegExp;
+ captureIndex: number;
}): { type: TType; value: string } | undefined => {
- const captures = scan({ regexp: pattern });
- if (!captures) {
- return undefined;
- }
+ const captures = scan({ regexp: pattern });
+ if (!captures) {
+ return undefined;
+ }
- return {
- type,
- value: captures[captureIndex],
- };
+ return {
+ type,
+ value: captures[captureIndex],
+ };
};
const scan = ({ regexp }: { regexp: RegExp }): RegExpExecArray | null => {
- const blankCaptures = /^[\n\r\t\s]+/.exec(input);
- if (blankCaptures) {
- consume({ size: blankCaptures[0].length });
- }
+ const blankCaptures = /^[\n\r\t\s]+/.exec(input);
+ if (blankCaptures) {
+ consume({ size: blankCaptures[0].length });
+ }
- const captures = regexp.exec(input);
- if (captures) {
- consume({ size: captures[0].length });
- }
+ const captures = regexp.exec(input);
+ if (captures) {
+ consume({ size: captures[0].length });
+ }
- return captures;
+ return captures;
};
const consume = ({ size }: { size: number }): void => {
- input = input.slice(size);
+ input = input.slice(size);
};
-export const parseGradient = ({ code }: { code: string }): Array => {
- input = code.toString().trim();
- if (input.endsWith(";")) {
- input = input.slice(0, -1);
- }
- return getAst();
+export const parseGradient = ({
+ code,
+}: {
+ code: string;
+}): Array => {
+ input = code.toString().trim();
+ if (input.endsWith(";")) {
+ input = input.slice(0, -1);
+ }
+ return getAst();
};
export const GradientParser = {
- parse: parseGradient,
-};
\ No newline at end of file
+ parse: parseGradient,
+};
diff --git a/apps/web/src/lib/iconify-api.ts b/apps/web/src/lib/iconify-api.ts
index 9d1bcb97..b5db1b9a 100644
--- a/apps/web/src/lib/iconify-api.ts
+++ b/apps/web/src/lib/iconify-api.ts
@@ -1,4 +1,3 @@
-
export const ICONIFY_HOSTS = [
"https://api.iconify.design",
"https://api.simplesvg.com",
@@ -61,7 +60,7 @@ export interface CollectionInfo {
}
export async function getCollections(
- category?: string
+ category?: string,
): Promise> {
try {
const response = await fetchWithFallback("/collections?pretty=1");
@@ -70,8 +69,8 @@ export async function getCollections(
if (category) {
const filtered = Object.fromEntries(
Object.entries(data).filter(
- ([_key, info]) => info.category === category
- )
+ ([_key, info]) => info.category === category,
+ ),
) as Record;
return filtered;
}
@@ -84,11 +83,11 @@ export async function getCollections(
}
export async function getCollection(
- prefix: string
+ prefix: string,
): Promise {
try {
const response = await fetchWithFallback(
- `/collection?prefix=${prefix}&pretty=1`
+ `/collection?prefix=${prefix}&pretty=1`,
);
return await response.json();
} catch (error) {
@@ -101,7 +100,7 @@ export async function searchIcons(
query: string,
limit: number = 64,
prefixes?: string[],
- category?: string
+ category?: string,
): Promise {
const params = new URLSearchParams({
query,
@@ -141,7 +140,7 @@ export function buildIconSvgUrl(
height?: number;
flip?: "horizontal" | "vertical" | "horizontal,vertical";
rotate?: number | string;
- }
+ },
): string {
const [prefix, name] = iconName.includes(":")
? iconName.split(":")
@@ -179,14 +178,14 @@ export function buildIconSvgUrl(
export function getIconSvgUrl(
iconName: string,
- params?: Parameters[2]
+ params?: Parameters[2],
): string {
return buildIconSvgUrl(currentHost, iconName, params);
}
export async function downloadSvgAsText(
iconName: string,
- params?: Parameters