Merge branch 'main' of https://github.com/mazeincoding/AppCut
This commit is contained in:
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: '[BUG] '
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
70
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
70
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
title: '[BUG] '
|
||||||
|
labels: bug
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: Platform
|
||||||
|
attributes:
|
||||||
|
label: Platform
|
||||||
|
description: Please enter the platform on which you encountered the bug.
|
||||||
|
placeholder: e.g. Windows 11, Ubuntu 14.04
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: Browser
|
||||||
|
attributes:
|
||||||
|
label: Browser
|
||||||
|
description: Please enter the browser on which you encountered the bug.
|
||||||
|
placeholder: e.g. Chrome 137, Firefox 137, Safari 17
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: current-behavior
|
||||||
|
attributes:
|
||||||
|
label: Current Behavior
|
||||||
|
description: A concise description of what you're experiencing.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: A concise description of what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: dropdown
|
||||||
|
id: recurrence-probability
|
||||||
|
attributes:
|
||||||
|
label: Recurrence Probability
|
||||||
|
description: How often does this bug occur?
|
||||||
|
options:
|
||||||
|
- Always
|
||||||
|
- Usually
|
||||||
|
- Sometimes
|
||||||
|
- Seldom
|
||||||
|
default: 0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: steps-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: Steps To Reproduce
|
||||||
|
description: Steps to reproduce the behavior.
|
||||||
|
placeholder: |
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: |
|
||||||
|
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||||
|
|
||||||
|
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||||
|
validations:
|
||||||
|
required: false
|
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: '[FEATURE] '
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
42
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
42
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an idea for OpenCut
|
||||||
|
title: '[FEATURE] '
|
||||||
|
labels: enhancement
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: Please make sure that no duplicated issues has already been delivered.
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Problem
|
||||||
|
placeholder: Is your feature request related to a problem? Please describe.
|
||||||
|
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Solution
|
||||||
|
placeholder: Describe the solution you'd like.
|
||||||
|
description: A clear and concise description of what you want to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternative
|
||||||
|
attributes:
|
||||||
|
label: Alternative
|
||||||
|
placeholder: Describe alternatives you've considered.
|
||||||
|
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: |
|
||||||
|
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||||
|
|
||||||
|
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||||
|
validations:
|
||||||
|
required: false
|
4
.github/workflows/bun-ci.yml
vendored
4
.github/workflows/bun-ci.yml
vendored
@ -31,13 +31,13 @@ jobs:
|
|||||||
- name: Install Bun
|
- name: Install Bun
|
||||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76
|
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.2
|
bun-version: 1.2.17
|
||||||
|
|
||||||
- name: Cache Bun modules
|
- name: Cache Bun modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.bun/install/cache
|
path: ~/.bun/install/cache
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('apps/web/bun.lock') }}
|
key: ${{ runner.os }}-bun-1.2.17-${{ hashFiles('apps/web/bun.lock') }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
working-directory: apps/web
|
working-directory: apps/web
|
||||||
|
@ -17,27 +17,28 @@ export function MediaPanel() {
|
|||||||
const { mediaItems, addMediaItem, removeMediaItem } = useMediaStore();
|
const { mediaItems, addMediaItem, removeMediaItem } = useMediaStore();
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [mediaFilter, setMediaFilter] = useState("all");
|
const [mediaFilter, setMediaFilter] = useState("all");
|
||||||
|
|
||||||
const processFiles = async (files: FileList | File[]) => {
|
const processFiles = async (files: FileList | File[]) => {
|
||||||
// If no files, do nothing
|
if (!files || files.length === 0) return;
|
||||||
if (!files?.length) return;
|
|
||||||
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
|
setProgress(0);
|
||||||
try {
|
try {
|
||||||
// Process files (extract metadata, generate thumbnails, etc.)
|
// Process files (extract metadata, generate thumbnails, etc.)
|
||||||
const items = await processMediaFiles(files);
|
const processedItems = await processMediaFiles(files, (p) =>
|
||||||
|
setProgress(p)
|
||||||
|
);
|
||||||
// Add each processed media item to the store
|
// Add each processed media item to the store
|
||||||
items.forEach((item) => {
|
processedItems.forEach((item) => addMediaItem(item));
|
||||||
addMediaItem(item);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Show error if processing fails
|
// Show error toast if processing fails
|
||||||
console.error("File processing failed:", error);
|
console.error("Error processing files:", error);
|
||||||
toast.error("Failed to process files");
|
toast.error("Failed to process files");
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
|
setProgress(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -241,15 +242,12 @@ export function MediaPanel() {
|
|||||||
{isProcessing ? (
|
{isProcessing ? (
|
||||||
<>
|
<>
|
||||||
<Upload className="h-4 w-4 animate-spin" />
|
<Upload className="h-4 w-4 animate-spin" />
|
||||||
<span className="hidden md:inline ml-2">Processing...</span>
|
<span className="hidden md:inline ml-2">{progress}%</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
<span
|
<span className="hidden sm:inline ml-2" aria-label="Add file">
|
||||||
className="hidden sm:inline ml-2"
|
|
||||||
aria-label="Add file"
|
|
||||||
>
|
|
||||||
Add
|
Add
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
|
@ -69,6 +69,7 @@ export function Timeline() {
|
|||||||
} = usePlaybackStore();
|
} = usePlaybackStore();
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
const [zoomLevel, setZoomLevel] = useState(1);
|
const [zoomLevel, setZoomLevel] = useState(1);
|
||||||
const dragCounterRef = useRef(0);
|
const dragCounterRef = useRef(0);
|
||||||
const timelineRef = useRef<HTMLDivElement>(null);
|
const timelineRef = useRef<HTMLDivElement>(null);
|
||||||
@ -334,8 +335,12 @@ export function Timeline() {
|
|||||||
} else if (e.dataTransfer.files?.length > 0) {
|
} else if (e.dataTransfer.files?.length > 0) {
|
||||||
// Handle file drops by creating new tracks
|
// Handle file drops by creating new tracks
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
|
setProgress(0);
|
||||||
try {
|
try {
|
||||||
const processedItems = await processMediaFiles(e.dataTransfer.files);
|
const processedItems = await processMediaFiles(
|
||||||
|
e.dataTransfer.files,
|
||||||
|
(p) => setProgress(p)
|
||||||
|
);
|
||||||
for (const processedItem of processedItems) {
|
for (const processedItem of processedItems) {
|
||||||
addMediaItem(processedItem);
|
addMediaItem(processedItem);
|
||||||
const currentMediaItems = useMediaStore.getState().mediaItems;
|
const currentMediaItems = useMediaStore.getState().mediaItems;
|
||||||
@ -363,6 +368,7 @@ export function Timeline() {
|
|||||||
toast.error("Failed to process dropped files");
|
toast.error("Failed to process dropped files");
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
|
setProgress(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -594,8 +600,9 @@ export function Timeline() {
|
|||||||
<div className="w-px h-6 bg-border mx-1" />
|
<div className="w-px h-6 bg-border mx-1" />
|
||||||
|
|
||||||
{/* Time Display */}
|
{/* Time Display */}
|
||||||
<div className="text-xs text-muted-foreground font-mono px-2"
|
<div
|
||||||
style={{ minWidth: '18ch', textAlign: 'center' }}
|
className="text-xs text-muted-foreground font-mono px-2"
|
||||||
|
style={{ minWidth: "18ch", textAlign: "center" }}
|
||||||
>
|
>
|
||||||
{currentTime.toFixed(1)}s / {duration.toFixed(1)}s
|
{currentTime.toFixed(1)}s / {duration.toFixed(1)}s
|
||||||
</div>
|
</div>
|
||||||
@ -946,14 +953,12 @@ export function Timeline() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isDragOver && (
|
{isDragOver && (
|
||||||
<div
|
<div className="absolute inset-0 z-20 flex items-center justify-center pointer-events-none backdrop-blur-lg">
|
||||||
className="absolute left-0 right-0 border-2 border-dashed border-accent flex items-center justify-center text-muted-foreground"
|
<div>
|
||||||
style={{
|
{isProcessing
|
||||||
top: `${tracks.length * 60}px`,
|
? `Processing ${progress}%`
|
||||||
height: "60px",
|
: "Drop media here to add to timeline"}
|
||||||
}}
|
</div>
|
||||||
>
|
|
||||||
<div>Drop media here to add a new track</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,11 +11,15 @@ import {
|
|||||||
export interface ProcessedMediaItem extends Omit<MediaItem, "id"> {}
|
export interface ProcessedMediaItem extends Omit<MediaItem, "id"> {}
|
||||||
|
|
||||||
export async function processMediaFiles(
|
export async function processMediaFiles(
|
||||||
files: FileList | File[]
|
files: FileList | File[],
|
||||||
|
onProgress?: (progress: number) => void
|
||||||
): Promise<ProcessedMediaItem[]> {
|
): Promise<ProcessedMediaItem[]> {
|
||||||
const fileArray = Array.from(files);
|
const fileArray = Array.from(files);
|
||||||
const processedItems: ProcessedMediaItem[] = [];
|
const processedItems: ProcessedMediaItem[] = [];
|
||||||
|
|
||||||
|
const total = fileArray.length;
|
||||||
|
let completed = 0;
|
||||||
|
|
||||||
for (const file of fileArray) {
|
for (const file of fileArray) {
|
||||||
const fileType = getFileType(file);
|
const fileType = getFileType(file);
|
||||||
|
|
||||||
@ -57,6 +61,15 @@ export async function processMediaFiles(
|
|||||||
duration,
|
duration,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Yield back to the event loop to keep the UI responsive
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|
||||||
|
completed += 1;
|
||||||
|
if (onProgress) {
|
||||||
|
const percent = Math.round((completed / total) * 100);
|
||||||
|
onProgress(percent);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error processing file:", file.name, error);
|
console.error("Error processing file:", file.name, error);
|
||||||
toast.error(`Failed to process ${file.name}`);
|
toast.error(`Failed to process ${file.name}`);
|
||||||
|
Reference in New Issue
Block a user