mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 15:38: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 '../styles/EditingTools.css';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface EditingToolsProps {
|
interface EditingToolsProps {
|
||||||
onSplit: () => void;
|
onSplit: () => void;
|
||||||
@ -29,6 +30,18 @@ const EditingTools = ({
|
|||||||
isPlaying = false,
|
isPlaying = false,
|
||||||
isPlayingSegments = false,
|
isPlayingSegments = false,
|
||||||
}: EditingToolsProps) => {
|
}: 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
|
// Handle play button click with iOS fix
|
||||||
const handlePlay = () => {
|
const handlePlay = () => {
|
||||||
// Ensure lastSeekedPosition is used when play is clicked
|
// Ensure lastSeekedPosition is used when play is clicked
|
||||||
@ -47,7 +60,7 @@ const EditingTools = ({
|
|||||||
<div className="button-group play-buttons-group">
|
<div className="button-group play-buttons-group">
|
||||||
{/* Play Segments button */}
|
{/* Play Segments button */}
|
||||||
<button
|
<button
|
||||||
className="button segments-button"
|
className={`button segments-button ${isPlayingSegments && isSmallScreen ? 'highlighted-stop' : ''}`}
|
||||||
onClick={onPlaySegments}
|
onClick={onPlaySegments}
|
||||||
data-tooltip={isPlayingSegments ? "Stop segments playback" : "Play all segments continuously until the end"}
|
data-tooltip={isPlayingSegments ? "Stop segments playback" : "Play all segments continuously until the end"}
|
||||||
style={{ fontSize: '0.875rem' }}
|
style={{ fontSize: '0.875rem' }}
|
||||||
@ -104,12 +117,13 @@ const EditingTools = ({
|
|||||||
</button> */}
|
</button> */}
|
||||||
|
|
||||||
{/* Standard Play button (only shown when not in preview mode or segments playback) */}
|
{/* Standard Play button (only shown when not in preview mode or segments playback) */}
|
||||||
{!isPreviewMode && !isPlayingSegments && (
|
{!isPreviewMode && (!isPlayingSegments || !isSmallScreen) && (
|
||||||
<button
|
<button
|
||||||
className="button play-button"
|
className={`button play-button ${isPlayingSegments ? 'greyed-out' : ''}`}
|
||||||
onClick={handlePlay}
|
onClick={handlePlay}
|
||||||
data-tooltip={isPlaying ? "Pause video" : "Play full video"}
|
data-tooltip={isPlaying ? "Pause video" : "Play full video"}
|
||||||
style={{ fontSize: '0.875rem' }}
|
style={{ fontSize: '0.875rem' }}
|
||||||
|
disabled={isPlayingSegments}
|
||||||
>
|
>
|
||||||
{isPlaying ? (
|
{isPlaying ? (
|
||||||
<>
|
<>
|
||||||
@ -135,7 +149,7 @@ const EditingTools = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Segments Playback message (replaces play button during segments playback) */}
|
{/* Segments Playback message (replaces play button during segments playback) */}
|
||||||
{isPlayingSegments && (
|
{isPlayingSegments && !isSmallScreen && (
|
||||||
<div className="segments-playback-message">
|
<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">
|
<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" />
|
<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
|
// 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) => {
|
const constrainTooltipPosition = (positionPercent: number) => {
|
||||||
// Default position logic (centered)
|
// Smooth transition zones instead of hard breakpoints
|
||||||
let leftValue = `${positionPercent}%`;
|
const leftTransitionStart = 0;
|
||||||
let transform = 'translateX(-50%)';
|
const leftTransitionEnd = 25;
|
||||||
|
const rightTransitionStart = 75;
|
||||||
|
const rightTransitionEnd = 100;
|
||||||
|
|
||||||
// Near left edge (first 17%)
|
let leftValue: string;
|
||||||
if (positionPercent < 17) {
|
let transform: string;
|
||||||
// Position the left edge of tooltip at 0%, no transform
|
|
||||||
|
if (positionPercent <= leftTransitionEnd) {
|
||||||
|
// Left side: smooth transition from center to left-aligned
|
||||||
|
if (positionPercent <= leftTransitionStart) {
|
||||||
|
// Fully left-aligned
|
||||||
leftValue = '0%';
|
leftValue = '0%';
|
||||||
transform = 'none';
|
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}%)`;
|
||||||
}
|
}
|
||||||
// Near right edge (last 17%)
|
} else if (positionPercent >= rightTransitionStart) {
|
||||||
else if (positionPercent > 83) {
|
// Right side: smooth transition from center to right-aligned
|
||||||
// Position the right edge of tooltip at 100%
|
if (positionPercent >= rightTransitionEnd) {
|
||||||
|
// Fully right-aligned
|
||||||
leftValue = '100%';
|
leftValue = '100%';
|
||||||
transform = 'translateX(-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 };
|
return { left: leftValue, transform };
|
||||||
@ -1111,8 +1136,8 @@ const TimelineControls = ({
|
|||||||
|
|
||||||
// Handle timeline click to seek and show a tooltip
|
// Handle timeline click to seek and show a tooltip
|
||||||
const handleTimelineClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleTimelineClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
// Prevent interaction if segments are playing
|
// Remove the check that prevents interaction during preview mode
|
||||||
if (isPlayingSegments) return;
|
// This allows users to click and jump in the timeline while previewing
|
||||||
|
|
||||||
if (!timelineRef.current || !scrollContainerRef.current) return;
|
if (!timelineRef.current || !scrollContainerRef.current) return;
|
||||||
|
|
||||||
@ -1243,8 +1268,8 @@ const TimelineControls = ({
|
|||||||
|
|
||||||
// Handle segment resize - works with both mouse and touch events
|
// Handle segment resize - works with both mouse and touch events
|
||||||
const handleSegmentResize = (segmentId: number, isLeft: boolean) => (e: React.MouseEvent | React.TouchEvent) => {
|
const handleSegmentResize = (segmentId: number, isLeft: boolean) => (e: React.MouseEvent | React.TouchEvent) => {
|
||||||
// Prevent interaction if segments are playing
|
// Remove the check that prevents interaction during preview mode
|
||||||
if (isPlayingSegments) return;
|
// This allows users to resize segments while previewing
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation(); // Prevent triggering parent's events
|
e.stopPropagation(); // Prevent triggering parent's events
|
||||||
@ -1579,8 +1604,8 @@ const TimelineControls = ({
|
|||||||
|
|
||||||
// Handle segment click to show the tooltip
|
// Handle segment click to show the tooltip
|
||||||
const handleSegmentClick = (segmentId: number) => (e: React.MouseEvent) => {
|
const handleSegmentClick = (segmentId: number) => (e: React.MouseEvent) => {
|
||||||
// Prevent interaction if segments are playing
|
// Remove the check that prevents interaction during preview mode
|
||||||
if (isPlayingSegments) return;
|
// This allows users to click segments while previewing
|
||||||
|
|
||||||
// Don't show tooltip if clicked on handle
|
// Don't show tooltip if clicked on handle
|
||||||
if ((e.target as HTMLElement).classList.contains('clip-segment-handle')) {
|
if ((e.target as HTMLElement).classList.contains('clip-segment-handle')) {
|
||||||
@ -2679,24 +2704,23 @@ const TimelineControls = ({
|
|||||||
{/* Second row with action buttons similar to segment tooltip */}
|
{/* Second row with action buttons similar to segment tooltip */}
|
||||||
<div className="tooltip-row tooltip-actions">
|
<div className="tooltip-row tooltip-actions">
|
||||||
{/* New segment button - Moved to first position */}
|
{/* New segment button - Moved to first position */}
|
||||||
{availableSegmentDuration >= 0.5 && (
|
|
||||||
<button
|
<button
|
||||||
className="tooltip-action-btn new-segment"
|
className={`tooltip-action-btn new-segment ${availableSegmentDuration < 0.5 ? 'disabled' : ''}`}
|
||||||
data-tooltip={`Create new segment`}
|
data-tooltip={availableSegmentDuration < 0.5 ? 'Not enough space for new segment' : 'Create new segment'}
|
||||||
|
disabled={availableSegmentDuration < 0.5}
|
||||||
onClick={async (e) => {
|
onClick={async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Only create if we have at least 0.5 seconds of space
|
||||||
|
if (availableSegmentDuration < 0.5) {
|
||||||
|
// Not enough space, do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new segment with the calculated available duration
|
// Create a new segment with the calculated available duration
|
||||||
const segmentStartTime = clickedTime;
|
const segmentStartTime = clickedTime;
|
||||||
const segmentEndTime = segmentStartTime + availableSegmentDuration;
|
const segmentEndTime = segmentStartTime + availableSegmentDuration;
|
||||||
|
|
||||||
// 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 the new segment with a generic name
|
// Create the new segment with a generic name
|
||||||
const newSegment: Segment = {
|
const newSegment: Segment = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
@ -2744,7 +2768,6 @@ const TimelineControls = ({
|
|||||||
New
|
New
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Go to start button - play from beginning of cutaway (until next segment) */}
|
{/* Go to start button - play from beginning of cutaway (until next segment) */}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -158,6 +158,32 @@
|
|||||||
font-size: 0.875rem !important;
|
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 */
|
/* Completely disable ALL hover effects for play buttons */
|
||||||
.play-button:hover:not(:disabled), .preview-button:hover:not(:disabled) {
|
.play-button:hover:not(:disabled), .preview-button:hover:not(:disabled) {
|
||||||
/* Reset everything to prevent any changes */
|
/* Reset everything to prevent any changes */
|
||||||
@ -237,6 +263,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@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 */
|
/* At this breakpoint, make preview button text shorter */
|
||||||
.preview-button {
|
.preview-button {
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
@ -260,11 +292,25 @@
|
|||||||
.button-group.play-buttons-group {
|
.button-group.play-buttons-group {
|
||||||
flex: initial;
|
flex: initial;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group.secondary {
|
.button-group.secondary {
|
||||||
flex: initial;
|
flex: initial;
|
||||||
justify-content: flex-end;
|
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 {
|
.flex-container.single-row {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix left-align for play buttons */
|
/* Fix left-align for play buttons */
|
||||||
@ -305,6 +352,14 @@
|
|||||||
|
|
||||||
/* Very small screens - maintain layout but reduce further */
|
/* Very small screens - maintain layout but reduce further */
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
|
.editing-tools-container {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-container.single-row {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.button-group.play-buttons-group,
|
.button-group.play-buttons-group,
|
||||||
.button-group.secondary {
|
.button-group.secondary {
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
@ -313,5 +368,49 @@
|
|||||||
.divider {
|
.divider {
|
||||||
display: none; /* Hide divider on very small screens */
|
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 {
|
.timeline-marker {
|
||||||
height: 52px; /* Increased height for touch devices */
|
height: 85px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-marker-head {
|
.timeline-marker-head {
|
||||||
@ -294,7 +294,7 @@
|
|||||||
.timeline-marker-drag {
|
.timeline-marker-drag {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
bottom: -18px; /* Adjusted for larger touch target */
|
bottom: -18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-marker-head.dragging {
|
.timeline-marker-head.dragging {
|
||||||
@ -854,8 +854,6 @@
|
|||||||
|
|
||||||
/* Segments playback mode styles */
|
/* Segments playback mode styles */
|
||||||
.segments-playback-mode {
|
.segments-playback-mode {
|
||||||
pointer-events: none;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.segments-playback-mode .timeline-container,
|
.segments-playback-mode .timeline-container,
|
||||||
@ -864,20 +862,14 @@
|
|||||||
.segments-playback-mode .timeline-marker-head,
|
.segments-playback-mode .timeline-marker-head,
|
||||||
.segments-playback-mode .timeline-marker-drag,
|
.segments-playback-mode .timeline-marker-drag,
|
||||||
.segments-playback-mode .trim-handle {
|
.segments-playback-mode .trim-handle {
|
||||||
pointer-events: none;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.segments-playback-mode .tooltip-action-btn {
|
.segments-playback-mode .tooltip-action-btn {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.segments-playback-mode .tooltip-time-btn {
|
.segments-playback-mode .tooltip-time-btn {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.segments-playback-mode .clip-segment:hover {
|
.segments-playback-mode .clip-segment:hover {
|
||||||
|
|||||||
@ -207,6 +207,26 @@
|
|||||||
white-space: nowrap;
|
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 */
|
/* Additional mobile optimizations */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.two-row-tooltip {
|
.two-row-tooltip {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user