Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/api/file.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ const getAllSharedFilesWithMe = async () => {
};

const getFileSystemTree = async () => {
const response = await apiClient.get("/file-flow/file/all");
const response = await apiClient.get("/file-flow/file/all?accessLevel=protected");
return response.data;
};


const getPrivateFiles = async () => {
const response = await apiClient.get("/file-flow/file/all?accessLevel=private");
return response.data;
};

Expand Down Expand Up @@ -77,6 +83,7 @@ export default {
renameFolder,
moveFileOrFolder,
createFile,
getPrivateFiles,
shareFileOrFolder,
getAllSharedFiles,
getAllSharedFilesByMe,
Expand Down
136 changes: 136 additions & 0 deletions src/components/player/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,51 @@ const customStyles = `
background-color: #ef4444;
}

/* Ensure timer is always visible */
.video-js .vjs-current-time,
.video-js .vjs-duration {
display: inline-block !important;
padding: 0 0.5em;
}

.video-js .vjs-time-divider {
display: inline-block !important;
padding: 0 0.2em;
}

/* Skip indicator styles */
.video-skip-indicator {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 2em;
font-weight: bold;
color: white;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
pointer-events: none;
z-index: 1000;
animation: fadeOut 0.5s ease-out forwards;
}

.video-skip-indicator.left {
left: 20%;
}

.video-skip-indicator.right {
right: 20%;
}

@keyframes fadeOut {
0% {
opacity: 1;
transform: translateY(-50%) scale(1.2);
}
100% {
opacity: 0;
transform: translateY(-50%) scale(1);
}
}

/* Mobile optimizations */
@media (max-width: 640px) {
.video-js .vjs-control-bar {
Expand Down Expand Up @@ -130,6 +175,7 @@ export function VideoPlayer({
const videoRef = useRef<HTMLVideoElement | null>(null);
const playerRef = useRef<any | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const doubleClickHandlerRef = useRef<((event: MouseEvent) => void) | null>(null);

useEffect(() => {
// Add custom styles to document
Expand Down Expand Up @@ -208,6 +254,49 @@ export function VideoPlayer({
if (onError) onError(error);
});

// Double-click handler for skip forward/backward
const handleDoubleClick = (event: MouseEvent) => {
const playerEl = player.el() as HTMLElement | null;
if (!playerEl) return;

const rect = playerEl.getBoundingClientRect();
const clickX = event.clientX - rect.left;
const playerWidth = rect.width;
const isLeftSide = clickX < playerWidth / 2;

const currentTime = player.currentTime();
const duration = player.duration();

if (typeof currentTime === 'number' && typeof duration === 'number') {
let newTime: number;
let skipText: string;

if (isLeftSide) {
// Skip backward 10 seconds
newTime = Math.max(0, currentTime - 10);
skipText = '-10s';
} else {
// Skip forward 10 seconds
newTime = Math.min(duration, currentTime + 10);
skipText = '+10s';
}

player.currentTime(newTime);

// Show skip indicator
showSkipIndicator(playerEl, skipText, isLeftSide);
}
};

// Store handler reference for cleanup
doubleClickHandlerRef.current = handleDoubleClick;

// Listen for double-click events on the player element
const playerEl = player.el() as HTMLElement | null;
if (playerEl) {
playerEl.addEventListener('dblclick', handleDoubleClick);
}

// Keyboard shortcuts
player.on('keydown', (e: any) => {
const event = e as KeyboardEvent;
Expand Down Expand Up @@ -268,13 +357,60 @@ export function VideoPlayer({
}

return () => {
// Clean up double-click listener
if (playerRef.current && doubleClickHandlerRef.current) {
const playerEl = playerRef.current.el() as HTMLElement | null;
if (playerEl) {
playerEl.removeEventListener('dblclick', doubleClickHandlerRef.current);
}
doubleClickHandlerRef.current = null;
}
if (playerRef.current) {
playerRef.current.dispose();
playerRef.current = null;
}
};
}, [url, autoplay, muted, controls, loop, preload, poster]);

// Helper function to show skip indicator
function showSkipIndicator(playerEl: HTMLElement, text: string, isLeft: boolean) {
// Remove existing indicator if any
const existingIndicator = playerEl.querySelector('.video-skip-indicator') as HTMLElement | null;
if (existingIndicator) {
existingIndicator.remove();
}

// Create new indicator
const indicator = document.createElement('div');
indicator.className = `video-skip-indicator ${isLeft ? 'left' : 'right'}`;
indicator.textContent = text;
indicator.style.position = 'absolute';
indicator.style.top = '50%';
indicator.style.transform = 'translateY(-50%)';
indicator.style.fontSize = '2em';
indicator.style.fontWeight = 'bold';
indicator.style.color = 'white';
indicator.style.textShadow = '2px 2px 4px rgba(0, 0, 0, 0.8)';
indicator.style.pointerEvents = 'none';
indicator.style.zIndex = '1000';
indicator.style.animation = 'fadeOut 0.5s ease-out forwards';

if (isLeft) {
indicator.style.left = '20%';
} else {
indicator.style.right = '20%';
}

playerEl.appendChild(indicator);

// Remove after animation
setTimeout(() => {
if (indicator.parentNode) {
indicator.remove();
}
}, 500);
}

// Helper function to determine video type
function getVideoType(url: string): string {
const extension = url.split('.').pop()?.toLowerCase();
Expand Down
161 changes: 89 additions & 72 deletions src/components/upload/FIleUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { useNavigate } from 'react-router-dom';
import { useUpload } from '@/contexts/UploadContext';
import { useFile } from '@/contexts/fileContext';
import type { AccessLevel } from '@/types/file.types';

export type FileType = 'excel' | 'pdf' | 'image' | 'video' | 'audio' | 'archive' | 'text' | 'any';

Expand All @@ -32,6 +33,7 @@ interface FileUploaderProps {
allowedTypes?: FileConfig[];
maxFiles?: number;
folderId?: string;
accessLevel?: AccessLevel; // 'public' | 'private' | 'protected'
}

const DEFAULT_FILE_CONFIGS: FileConfig[] = [
Expand All @@ -46,6 +48,7 @@ const FileUploader: React.FC<FileUploaderProps> = ({
allowedTypes = DEFAULT_FILE_CONFIGS,
maxFiles = 10,
folderId,
accessLevel,
}) => {
const { createFile } = useFile();
const navigate = useNavigate();
Expand All @@ -64,7 +67,7 @@ const FileUploader: React.FC<FileUploaderProps> = ({
autoClearCompleted
} = useUpload();

const { fileStates } = state;
const { fileStates, error: uploadError } = state;

// Check if any uploads are in progress
useEffect(() => {
Expand Down Expand Up @@ -173,81 +176,95 @@ const FileUploader: React.FC<FileUploaderProps> = ({
const fileType = getFileType(file);

try {
if (fileType === 'video') {
// Use chunked upload for video files
const result = await handleUpload(file, folderId);
if (result) {
await createFile({
name: file.name,
parent_id: folderId === "root" ? null : folderId,
file_info: result
});
updateFileState(file.name, {
url: result.storage_path,
status: 'completed',
progress: 100,
lastUploadedChunk: Math.ceil(file.size / (5 * 1024 * 1024))
});
setCompletedFiles(prev => new Set([...prev, file.name]));
switch (fileType) {
case 'video': {
// Use chunked upload for video files
const result = await handleUpload(file, folderId);
if (result) {
await createFile({
name: file.name,
parent_id: folderId === "root" ? null : folderId,
access_level: accessLevel,
file_info: result
});
updateFileState(file.name, {
url: result.storage_path,
status: 'completed',
progress: 100,
lastUploadedChunk: Math.ceil(file.size / (5 * 1024 * 1024))
});
setCompletedFiles(prev => new Set([...prev, file.name]));
}
break;
}
} else {
// Use direct upload for non-video files (images, excel, pdf, text, etc.)
// Initialize file state if not exists
if (!fileStates[file.name]) {
updateFileState(file.name, {
uploadId: null,
url: null,
fileKey: null,
progress: 0,
status: 'uploading',
error: null,
lastUploadedChunk: 0,
totalChunks: 1,
fileName: file.name,
fileSize: file.size,
fileType: file.type
});
} else {
case 'excel':
case 'pdf':
case 'image':
case 'audio':
case 'archive':
case 'text':
case 'any':
default: {
// Use direct upload for non-video files (images, excel, pdf, text, etc.)
// Initialize file state if not exists
if (!fileStates[file.name]) {
updateFileState(file.name, {
uploadId: null,
url: null,
fileKey: null,
progress: 0,
status: 'uploading',
error: null,
lastUploadedChunk: 0,
totalChunks: 1,
fileName: file.name,
fileSize: file.size,
fileType: file.type
});
} else {
updateFileState(file.name, {
status: 'uploading',
progress: 0,
error: null
});
}

// Update progress to show upload started
updateFileState(file.name, {
status: 'uploading',
progress: 0,
error: null
});
}

// Update progress to show upload started
updateFileState(file.name, {
progress: 50
});

const uploadedFiles = await uploadFiles([file]);

if (uploadedFiles && uploadedFiles.length > 0) {
const uploadedFile = uploadedFiles[0];

// Create file entry in database
await createFile({
name: file.name,
parent_id: folderId === "root" ? null : folderId,
file_info: {
file_type: file.type,
file_size: file.size,
storage_path: uploadedFile.url,
thumbnail_path: uploadedFile.url,
duration: undefined
}
progress: 50
});

updateFileState(file.name, {
url: uploadedFile.url,
status: 'completed',
progress: 100,
lastUploadedChunk: 1,
totalChunks: 1 // Set to 1 for direct uploads
});
setCompletedFiles(prev => new Set([...prev, file.name]));
} else {
throw new Error('Upload failed - no file returned');
const uploadedFiles = await uploadFiles([file]);

if (uploadedFiles && uploadedFiles.length > 0) {
const uploadedFile = uploadedFiles[0];

// Create file entry in database
await createFile({
name: file.name,
parent_id: folderId === "root" ? null : folderId,
access_level: accessLevel,
file_info: {
file_type: file.type,
file_size: file.size,
storage_path: uploadedFile.url,
thumbnail_path: uploadedFile.url,
duration: undefined
}
});

updateFileState(file.name, {
url: uploadedFile.url,
status: 'completed',
progress: 100,
lastUploadedChunk: 1,
totalChunks: 1 // Set to 1 for direct uploads
});
setCompletedFiles(prev => new Set([...prev, file.name]));
} else {
throw new Error(uploadError || 'Upload failed - no file returned');
}
break;
}
}
} catch (error: any) {
Expand Down
Loading