mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 07:28:53 -05:00
- keep icons in popup menu but do not allow to click (rather than disapear) #61 - New item: When activating Play Preview #62 - dragable ball on mobile #63 - mobile: dragging the popup menu small issue #64 - mobile vertical direction #65
This commit is contained in:
parent
b6a197a00f
commit
58c0247f6b
@ -1,4 +1,5 @@
|
||||
import '../styles/EditingTools.css';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface EditingToolsProps {
|
||||
onSplit: () => void;
|
||||
@ -29,6 +30,18 @@ const EditingTools = ({
|
||||
isPlaying = false,
|
||||
isPlayingSegments = false,
|
||||
}: EditingToolsProps) => {
|
||||
const [isSmallScreen, setIsSmallScreen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkScreenSize = () => {
|
||||
setIsSmallScreen(window.innerWidth <= 640);
|
||||
};
|
||||
|
||||
checkScreenSize();
|
||||
window.addEventListener('resize', checkScreenSize);
|
||||
return () => window.removeEventListener('resize', checkScreenSize);
|
||||
}, []);
|
||||
|
||||
// Handle play button click with iOS fix
|
||||
const handlePlay = () => {
|
||||
// Ensure lastSeekedPosition is used when play is clicked
|
||||
@ -47,7 +60,7 @@ const EditingTools = ({
|
||||
<div className="button-group play-buttons-group">
|
||||
{/* Play Segments button */}
|
||||
<button
|
||||
className="button segments-button"
|
||||
className={`button segments-button ${isPlayingSegments && isSmallScreen ? 'highlighted-stop' : ''}`}
|
||||
onClick={onPlaySegments}
|
||||
data-tooltip={isPlayingSegments ? "Stop segments playback" : "Play all segments continuously until the end"}
|
||||
style={{ fontSize: '0.875rem' }}
|
||||
@ -104,12 +117,13 @@ const EditingTools = ({
|
||||
</button> */}
|
||||
|
||||
{/* Standard Play button (only shown when not in preview mode or segments playback) */}
|
||||
{!isPreviewMode && !isPlayingSegments && (
|
||||
{!isPreviewMode && (!isPlayingSegments || !isSmallScreen) && (
|
||||
<button
|
||||
className="button play-button"
|
||||
className={`button play-button ${isPlayingSegments ? 'greyed-out' : ''}`}
|
||||
onClick={handlePlay}
|
||||
data-tooltip={isPlaying ? "Pause video" : "Play full video"}
|
||||
style={{ fontSize: '0.875rem' }}
|
||||
disabled={isPlayingSegments}
|
||||
>
|
||||
{isPlaying ? (
|
||||
<>
|
||||
@ -135,7 +149,7 @@ const EditingTools = ({
|
||||
)}
|
||||
|
||||
{/* Segments Playback message (replaces play button during segments playback) */}
|
||||
{isPlayingSegments && (
|
||||
{isPlayingSegments && !isSmallScreen && (
|
||||
<div className="segments-playback-message">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
|
||||
@ -53,22 +53,47 @@ interface TimelineControlsProps {
|
||||
}
|
||||
|
||||
// Function to calculate and constrain tooltip position to keep it on screen
|
||||
// Uses smooth transitions instead of hard breakpoints to eliminate jumping
|
||||
const constrainTooltipPosition = (positionPercent: number) => {
|
||||
// Default position logic (centered)
|
||||
let leftValue = `${positionPercent}%`;
|
||||
let transform = 'translateX(-50%)';
|
||||
// Smooth transition zones instead of hard breakpoints
|
||||
const leftTransitionStart = 0;
|
||||
const leftTransitionEnd = 25;
|
||||
const rightTransitionStart = 75;
|
||||
const rightTransitionEnd = 100;
|
||||
|
||||
// Near left edge (first 17%)
|
||||
if (positionPercent < 17) {
|
||||
// Position the left edge of tooltip at 0%, no transform
|
||||
leftValue = '0%';
|
||||
transform = 'none';
|
||||
}
|
||||
// Near right edge (last 17%)
|
||||
else if (positionPercent > 83) {
|
||||
// Position the right edge of tooltip at 100%
|
||||
leftValue = '100%';
|
||||
transform = 'translateX(-100%)';
|
||||
let leftValue: string;
|
||||
let transform: string;
|
||||
|
||||
if (positionPercent <= leftTransitionEnd) {
|
||||
// Left side: smooth transition from center to left-aligned
|
||||
if (positionPercent <= leftTransitionStart) {
|
||||
// Fully left-aligned
|
||||
leftValue = '0%';
|
||||
transform = 'none';
|
||||
} else {
|
||||
// Smooth transition zone
|
||||
const transitionProgress = (positionPercent - leftTransitionStart) / (leftTransitionEnd - leftTransitionStart);
|
||||
const translateAmount = -50 * transitionProgress; // Gradually reduce from 0% to -50%
|
||||
leftValue = `${positionPercent}%`;
|
||||
transform = `translateX(${translateAmount}%)`;
|
||||
}
|
||||
} else if (positionPercent >= rightTransitionStart) {
|
||||
// Right side: smooth transition from center to right-aligned
|
||||
if (positionPercent >= rightTransitionEnd) {
|
||||
// Fully right-aligned
|
||||
leftValue = '100%';
|
||||
transform = 'translateX(-100%)';
|
||||
} else {
|
||||
// Smooth transition zone
|
||||
const transitionProgress = (positionPercent - rightTransitionStart) / (rightTransitionEnd - rightTransitionStart);
|
||||
const translateAmount = -50 - (50 * transitionProgress); // Gradually change from -50% to -100%
|
||||
leftValue = `${positionPercent}%`;
|
||||
transform = `translateX(${translateAmount}%)`;
|
||||
}
|
||||
} else {
|
||||
// Center zone: normal centered positioning
|
||||
leftValue = `${positionPercent}%`;
|
||||
transform = 'translateX(-50%)';
|
||||
}
|
||||
|
||||
return { left: leftValue, transform };
|
||||
@ -1111,8 +1136,8 @@ const TimelineControls = ({
|
||||
|
||||
// Handle timeline click to seek and show a tooltip
|
||||
const handleTimelineClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
// Prevent interaction if segments are playing
|
||||
if (isPlayingSegments) return;
|
||||
// Remove the check that prevents interaction during preview mode
|
||||
// This allows users to click and jump in the timeline while previewing
|
||||
|
||||
if (!timelineRef.current || !scrollContainerRef.current) return;
|
||||
|
||||
@ -1243,8 +1268,8 @@ const TimelineControls = ({
|
||||
|
||||
// Handle segment resize - works with both mouse and touch events
|
||||
const handleSegmentResize = (segmentId: number, isLeft: boolean) => (e: React.MouseEvent | React.TouchEvent) => {
|
||||
// Prevent interaction if segments are playing
|
||||
if (isPlayingSegments) return;
|
||||
// Remove the check that prevents interaction during preview mode
|
||||
// This allows users to resize segments while previewing
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Prevent triggering parent's events
|
||||
@ -1579,8 +1604,8 @@ const TimelineControls = ({
|
||||
|
||||
// Handle segment click to show the tooltip
|
||||
const handleSegmentClick = (segmentId: number) => (e: React.MouseEvent) => {
|
||||
// Prevent interaction if segments are playing
|
||||
if (isPlayingSegments) return;
|
||||
// Remove the check that prevents interaction during preview mode
|
||||
// This allows users to click segments while previewing
|
||||
|
||||
// Don't show tooltip if clicked on handle
|
||||
if ((e.target as HTMLElement).classList.contains('clip-segment-handle')) {
|
||||
@ -2679,72 +2704,70 @@ const TimelineControls = ({
|
||||
{/* Second row with action buttons similar to segment tooltip */}
|
||||
<div className="tooltip-row tooltip-actions">
|
||||
{/* New segment button - Moved to first position */}
|
||||
{availableSegmentDuration >= 0.5 && (
|
||||
<button
|
||||
className="tooltip-action-btn new-segment"
|
||||
data-tooltip={`Create new segment`}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
<button
|
||||
className={`tooltip-action-btn new-segment ${availableSegmentDuration < 0.5 ? 'disabled' : ''}`}
|
||||
data-tooltip={availableSegmentDuration < 0.5 ? 'Not enough space for new segment' : 'Create new segment'}
|
||||
disabled={availableSegmentDuration < 0.5}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Create a new segment with the calculated available duration
|
||||
const segmentStartTime = clickedTime;
|
||||
const segmentEndTime = segmentStartTime + availableSegmentDuration;
|
||||
// Only create if we have at least 0.5 seconds of space
|
||||
if (availableSegmentDuration < 0.5) {
|
||||
// Not enough space, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
// Only create if we have at least 0.5 seconds of space
|
||||
if (availableSegmentDuration < 0.5) {
|
||||
// Not enough space, close tooltip
|
||||
setShowEmptySpaceTooltip(false);
|
||||
return;
|
||||
// Create a new segment with the calculated available duration
|
||||
const segmentStartTime = clickedTime;
|
||||
const segmentEndTime = segmentStartTime + availableSegmentDuration;
|
||||
|
||||
// Create the new segment with a generic name
|
||||
const newSegment: Segment = {
|
||||
id: Date.now(),
|
||||
name: `segment`,
|
||||
startTime: segmentStartTime,
|
||||
endTime: segmentEndTime,
|
||||
thumbnail: '' // Empty placeholder - we'll use dynamic colors instead
|
||||
};
|
||||
|
||||
// Add the new segment to existing segments
|
||||
const updatedSegments = [...clipSegments, newSegment];
|
||||
|
||||
// Create and dispatch the update event
|
||||
const updateEvent = new CustomEvent('update-segments', {
|
||||
detail: {
|
||||
segments: updatedSegments,
|
||||
recordHistory: true, // Explicitly record this action in history
|
||||
action: 'create_segment'
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(updateEvent);
|
||||
|
||||
// Create the new segment with a generic name
|
||||
const newSegment: Segment = {
|
||||
id: Date.now(),
|
||||
name: `segment`,
|
||||
startTime: segmentStartTime,
|
||||
endTime: segmentEndTime,
|
||||
thumbnail: '' // Empty placeholder - we'll use dynamic colors instead
|
||||
};
|
||||
// Close empty space tooltip
|
||||
setShowEmptySpaceTooltip(false);
|
||||
|
||||
// Add the new segment to existing segments
|
||||
const updatedSegments = [...clipSegments, newSegment];
|
||||
// After creating the segment, wait a short time for the state to update
|
||||
setTimeout(() => {
|
||||
// The newly created segment is the last one in the array with the ID we just assigned
|
||||
const createdSegment = updatedSegments[updatedSegments.length - 1];
|
||||
|
||||
// Create and dispatch the update event
|
||||
const updateEvent = new CustomEvent('update-segments', {
|
||||
detail: {
|
||||
segments: updatedSegments,
|
||||
recordHistory: true, // Explicitly record this action in history
|
||||
action: 'create_segment'
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(updateEvent);
|
||||
|
||||
// Close empty space tooltip
|
||||
setShowEmptySpaceTooltip(false);
|
||||
|
||||
// After creating the segment, wait a short time for the state to update
|
||||
setTimeout(() => {
|
||||
// The newly created segment is the last one in the array with the ID we just assigned
|
||||
const createdSegment = updatedSegments[updatedSegments.length - 1];
|
||||
|
||||
if (createdSegment) {
|
||||
// Set this segment as selected to show its tooltip
|
||||
setSelectedSegmentId(createdSegment.id);
|
||||
logger.debug("Created and selected new segment:", createdSegment.id);
|
||||
}
|
||||
}, 100); // Small delay to ensure state is updated
|
||||
}}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="12" y1="8" x2="12" y2="16"></line>
|
||||
<line x1="8" y1="12" x2="16" y2="12"></line>
|
||||
</svg>
|
||||
<span className="tooltip-btn-text">
|
||||
New
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
if (createdSegment) {
|
||||
// Set this segment as selected to show its tooltip
|
||||
setSelectedSegmentId(createdSegment.id);
|
||||
logger.debug("Created and selected new segment:", createdSegment.id);
|
||||
}
|
||||
}, 100); // Small delay to ensure state is updated
|
||||
}}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="12" y1="8" x2="12" y2="16"></line>
|
||||
<line x1="8" y1="12" x2="16" y2="12"></line>
|
||||
</svg>
|
||||
<span className="tooltip-btn-text">
|
||||
New
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* Go to start button - play from beginning of cutaway (until next segment) */}
|
||||
<button
|
||||
|
||||
@ -158,6 +158,32 @@
|
||||
font-size: 0.875rem !important;
|
||||
}
|
||||
|
||||
/* Greyed out play button when segments are playing */
|
||||
.play-button.greyed-out {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Highlighted stop button with blue pulse on small screens */
|
||||
.segments-button.highlighted-stop {
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
color: #3b82f6;
|
||||
border: 1px solid #3b82f6;
|
||||
animation: bluePulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes bluePulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 8px rgba(59, 130, 246, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Completely disable ALL hover effects for play buttons */
|
||||
.play-button:hover:not(:disabled), .preview-button:hover:not(:disabled) {
|
||||
/* Reset everything to prevent any changes */
|
||||
@ -237,6 +263,12 @@
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
/* Prevent container overflow on mobile */
|
||||
.editing-tools-container {
|
||||
padding: 0.75rem;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* At this breakpoint, make preview button text shorter */
|
||||
.preview-button {
|
||||
min-width: auto;
|
||||
@ -260,11 +292,25 @@
|
||||
.button-group.play-buttons-group {
|
||||
flex: initial;
|
||||
justify-content: flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.button-group.secondary {
|
||||
flex: initial;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Reduce button sizes on mobile */
|
||||
.button-group button {
|
||||
padding: 0.375rem;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.button-group button svg {
|
||||
height: 1.125rem;
|
||||
width: 1.125rem;
|
||||
margin-right: 0.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,6 +319,7 @@
|
||||
.flex-container.single-row {
|
||||
justify-content: space-between;
|
||||
flex-wrap: nowrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Fix left-align for play buttons */
|
||||
@ -305,6 +352,14 @@
|
||||
|
||||
/* Very small screens - maintain layout but reduce further */
|
||||
@media (max-width: 480px) {
|
||||
.editing-tools-container {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.flex-container.single-row {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.button-group.play-buttons-group,
|
||||
.button-group.secondary {
|
||||
gap: 0.25rem;
|
||||
@ -313,5 +368,49 @@
|
||||
.divider {
|
||||
display: none; /* Hide divider on very small screens */
|
||||
}
|
||||
|
||||
/* Even smaller buttons on very small screens */
|
||||
.button-group button {
|
||||
padding: 0.125rem;
|
||||
}
|
||||
|
||||
.button-group button svg {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* Hide all button text on very small screens */
|
||||
.button-text,
|
||||
.reset-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Portrait orientation specific fixes */
|
||||
@media (max-width: 640px) and (orientation: portrait) {
|
||||
.editing-tools-container {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.flex-container.single-row {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Ensure button groups don't overflow */
|
||||
.button-group {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.button-group.play-buttons-group {
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
.button-group.secondary {
|
||||
max-width: 40%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -282,7 +282,7 @@
|
||||
}
|
||||
|
||||
.timeline-marker {
|
||||
height: 52px; /* Increased height for touch devices */
|
||||
height: 85px;
|
||||
}
|
||||
|
||||
.timeline-marker-head {
|
||||
@ -294,7 +294,7 @@
|
||||
.timeline-marker-drag {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
bottom: -18px; /* Adjusted for larger touch target */
|
||||
bottom: -18px;
|
||||
}
|
||||
|
||||
.timeline-marker-head.dragging {
|
||||
@ -854,8 +854,6 @@
|
||||
|
||||
/* Segments playback mode styles */
|
||||
.segments-playback-mode {
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.segments-playback-mode .timeline-container,
|
||||
@ -864,20 +862,14 @@
|
||||
.segments-playback-mode .timeline-marker-head,
|
||||
.segments-playback-mode .timeline-marker-drag,
|
||||
.segments-playback-mode .trim-handle {
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.segments-playback-mode .tooltip-action-btn {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.segments-playback-mode .tooltip-time-btn {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.segments-playback-mode .clip-segment:hover {
|
||||
|
||||
@ -207,6 +207,26 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Disabled state for tooltip action buttons */
|
||||
.tooltip-action-btn.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.tooltip-action-btn.disabled:hover {
|
||||
background-color: #f3f4f6;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.tooltip-action-btn.disabled svg {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.tooltip-action-btn.disabled .tooltip-btn-text {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Additional mobile optimizations */
|
||||
@media (max-width: 768px) {
|
||||
.two-row-tooltip {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user