diff --git a/app/styles/Tagging.module.css b/app/styles/Tagging.module.css
index a0deb69..e78a017 100644
--- a/app/styles/Tagging.module.css
+++ b/app/styles/Tagging.module.css
@@ -1,7 +1,265 @@
+/* Tagging.module.css */
+
.container {
- padding: 10px;
+ padding: 15px; /* Reduced padding */
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background-color: #ffffff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ border-radius: 8px;
+}
+
+/* Minimalistic Video ID Input Line */
+.videoIdLine {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+.videoIdLine input {
+ flex: 1.5;
+ padding: 6px 10px;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ font-size: 14px;
+}
+
+.videoIdLine button {
+ padding: 6px 12px;
+ border: none;
+ border-radius: 4px;
+ background-color: #0070f3;
+ color: #ffffff;
+ font-size: 14px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.videoIdLine button:hover {
+ background-color: #005bb5;
+}
+
+.topSection {
+ display: flex;
+ flex-direction: row;
+ gap: 20px;
+ align-items: flex-start;
+}
+
+.videoSection {
+ flex: 4; /* 60% of the container's width (assuming total flex is 5) */
+ display: flex;
+ flex-direction: column;
+ gap: 10px; /* Space between elements */
+}
+
+.videoPlayer {
+ width: 100%;
+ aspect-ratio: 16 / 9; /* Maintains aspect ratio */
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.timestampTableContainer {
+ flex: 1; /* Matches the height of the video player */
+ overflow-y: auto; /* Adds scrollbar if content overflows */
+}
+
+.timestampTable {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.timestampTable th,
+.timestampTable td {
+ padding: 8px;
+ border: 1px solid #dddddd;
+ text-align: center;
+ font-size: 14px;
+}
+
+.timestampTable th {
+ background-color: #e0e0e0;
+ font-weight: 600;
+}
+
+.inputGroup {
+ margin-top: 15px;
+ display: flex;
+ flex-direction: column;
+}
+
+.inputGroup label {
+ margin-bottom: 5px;
+ font-weight: 600;
+ color: #333333;
+}
+
+.inputGroup input {
+ padding: 8px 12px;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ font-size: 14px;
+}
+
+.buttonsGroup {
+ margin-top: 15px;
+ display: flex;
+ gap: 10px;
+}
+
+.buttonsGroup button {
+ padding: 10px 16px;
+ border: none;
+ border-radius: 4px;
+ background-color: #0070f3;
+ color: #ffffff;
+ font-size: 14px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.buttonsGroup button:hover {
+ background-color: #005bb5;
}
-.table {
+.courtSection {
+ flex: 2; /* 40% of the container's width (assuming total flex is 5) */
display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10px; /* Smaller padding */
+}
+
+.courtImage {
+ width: 100%;
+ height: 100%;
+ max-height: 100%;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: transform 0.2s ease;
+}
+
+.courtImage:hover {
+ transform: scale(1.05);
+}
+
+.controlsSection {
+ flex: 2; /* Adjust as needed */
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+
+.timerTable {
+ width: 100%; /* Full width of controlsSection */
+ margin-top: 20px;
+ border-collapse: collapse;
+}
+
+.timerTable td {
+ padding: 6px; /* Smaller padding */
+ border: 1px solid #dddddd;
+ font-size: 14px;
+}
+
+.inputField {
+ width: 100%;
+ padding: 6px 10px;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ font-size: 14px;
+}
+
+.keybindingsTable {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.keybindingsTable th,
+.keybindingsTable td {
+ padding: 8px;
+ border: 1px solid #dddddd;
+ text-align: left;
+ font-size: 14px;
+}
+
+.keybindingsTable th {
+ background-color: #e0e0e0;
+ font-weight: 600;
+}
+
+.clickedInfo {
+ padding: 12px;
+ border: 1px solid #dddddd;
+ border-radius: 6px;
+ background-color: #fafafa;
+}
+
+.clickedInfo h3 {
+ margin-bottom: 10px;
+ font-size: 16px;
+ color: #333333;
+}
+
+.clickedInfo p {
+ margin: 5px 0;
+ font-size: 14px;
+ color: #555555;
+}
+
+.customHr {
+ border: none;
+ height: 2px;
+ background-color: #e0e0e0;
+ margin: 30px 0;
+}
+
+.deleteButton {
+ padding: 6px 10px;
+ background-color: #e74c3c;
+ border: none;
+ border-radius: 4px;
+ color: #ffffff;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.deleteButton:hover {
+ background-color: #c0392b;
+}
+
+@media (max-width: 1200px) {
+ .topSection {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .courtSection {
+ flex: 1;
+ width: 100%;
+ }
+
+ .videoSection,
+ .controlsSection {
+ width: 100%;
+ }
+
+ .timestampTableContainer {
+ height: 200px; /* Adjust as needed for smaller screens */
+ }
+
+ .videoIdLine input {
+ width: 80%;
+ }
+}
+
+@media (max-width: 768px) {
+ .buttonsGroup {
+ flex-direction: column;
+ }
+
+ .buttonsGroup button {
+ width: 100%;
+ }
}
diff --git a/app/timestamp-tagger/page.js b/app/timestamp-tagger/page.js
index f0d3706..0785961 100644
--- a/app/timestamp-tagger/page.js
+++ b/app/timestamp-tagger/page.js
@@ -2,9 +2,11 @@
import React, { useState, useEffect, useRef, Suspense } from 'react'
import { useSearchParams } from 'next/navigation'
-import VideoPlayer from '@/app/components/VideoPlayer'
+import VideoPlayer from '../components/VideoPlayer.js'
+import TennisCourtSVG from '@/public/TennisCourtSVG.js'
import styles from '@/app/styles/Tagging.module.css'
+// TagTable Component remains unchanged
const TagTable = ({
pair,
index,
@@ -21,6 +23,8 @@ const TagTable = ({
type="text"
value={pair[0]}
onChange={(event) => handleStartTimeChange(index, event.target.value)}
+ className={styles.inputField}
+ placeholder="Start Time (ms)"
/>
@@ -28,6 +32,8 @@ const TagTable = ({
type="text"
value={pair[1]}
onChange={(event) => handleEndTimeChange(index, event.target.value)}
+ className={styles.inputField}
+ placeholder="End Time (ms)"
/>
|
@@ -35,14 +41,17 @@ const TagTable = ({
type="text"
value={pair[2]}
onChange={(event) => handlePlayerWonChange(index, event.target.value)}
+ className={styles.inputField}
+ placeholder="Point Winner"
/>
|
|
@@ -51,39 +60,35 @@ const TagTable = ({
const KeybindingsTable = () => {
return (
-
+
-
- Key
- |
-
- Action
- |
+ Key |
+ Action |
- [space] |
+ [Space] |
Pause/Play |
- [d] [f] [g] |
+ [D] [F] [G] |
Start Timestamp | End Timestamp, Player 1 Won | End Timestamp,
Player 2 Won
|
- [r] [e] |
+ [R] [E] |
Forward | Backward 1s |
- [w] [q] |
+ [W] [Q] |
Forward | Backward 5s |
- [s] [a] |
+ [S] [A] |
Forward | Backward 10s |
@@ -104,16 +109,35 @@ export default function TagMatch() {
const FRAMERATE = 30
const inputRef = useRef(null)
+ // State variables for clicked point
+ const [clickedQuadrant, setClickedQuadrant] = useState(null)
+ const [clickedCoordinates, setClickedCoordinates] = useState(null)
+
const searchParams = useSearchParams()
useEffect(() => {
- setVideoId(searchParams.get('videoId'))
- }, [])
+ const initialVideoId = searchParams.get('videoId') || ''
+ setVideoId(initialVideoId)
+ }, [searchParams])
const handleVideoIdChange = (event) => {
setVideoId(event.target.value)
}
+ const handleLoadVideo = () => {
+ if (videoId.trim() === '') {
+ alert('Please enter a valid YouTube Video ID.')
+ return
+ }
+ // Optionally, you can update the URL's search params here
+ // or handle any additional logic when loading a new video
+ // For example, you might want to reset timeList and timerValue
+ setTimeList([])
+ setTimerValue(0)
+ setCurTimeStart(0)
+ // If using URL search params, you might need to update them
+ }
+
const handleKeyDown = (event) => {
if (!videoObject) return
@@ -124,6 +148,8 @@ export default function TagMatch() {
return
}
+ const key = event.key.toLowerCase()
+
const keyActions = {
' ': () => {
const playing = videoObject.getPlayerState() === 1
@@ -132,18 +158,16 @@ export default function TagMatch() {
d: () => {
const newTimestamp = Math.round(videoObject.getCurrentTime() * 1000)
if (!timeList.some((pair) => pair[1] === 0)) {
- setTimeList((timeList) =>
- [...timeList, [newTimestamp, 0, '']].sort(
- (pair1, pair2) => pair1[0] - pair2[0]
- )
+ setTimeList((prevList) =>
+ [...prevList, [newTimestamp, 0, '']].sort((a, b) => a[0] - b[0])
)
setCurTimeStart(newTimestamp)
}
},
f: () => {
const newTimestamp = Math.round(videoObject.getCurrentTime() * 1000)
- setTimeList((timeList) =>
- timeList.map((pair) =>
+ setTimeList((prevList) =>
+ prevList.map((pair) =>
pair[1] === 0 && newTimestamp >= pair[0]
? [pair[0], newTimestamp, 'Player 1']
: pair
@@ -152,8 +176,8 @@ export default function TagMatch() {
},
g: () => {
const newTimestamp = Math.round(videoObject.getCurrentTime() * 1000)
- setTimeList((timeList) =>
- timeList.map((pair) =>
+ setTimeList((prevList) =>
+ prevList.map((pair) =>
pair[1] === 0 && newTimestamp >= pair[0]
? [pair[0], newTimestamp, 'Player 2']
: pair
@@ -168,41 +192,29 @@ export default function TagMatch() {
q: () => videoObject.seekTo(videoObject.getCurrentTime() - 5, true),
s: () => videoObject.seekTo(videoObject.getCurrentTime() + 10, true),
a: () => videoObject.seekTo(videoObject.getCurrentTime() - 10, true),
- 2: () => videoObject.setPlaybackRate(2),
- 1: () => videoObject.setPlaybackRate(1)
+ 1: () => videoObject.setPlaybackRate(1),
+ 2: () => videoObject.setPlaybackRate(2)
}
- const action = keyActions[event.key]
+ const action = keyActions[key]
if (action) action()
}
const handleStartTimeChange = (index, value) => {
const updatedTimeList = [...timeList]
- updatedTimeList[index] = [
- parseInt(value),
- updatedTimeList[index][1],
- updatedTimeList[index][2]
- ]
+ updatedTimeList[index][0] = parseInt(value) || 0
setTimeList(updatedTimeList)
}
const handleEndTimeChange = (index, value) => {
const updatedTimeList = [...timeList]
- updatedTimeList[index] = [
- updatedTimeList[index][0],
- parseInt(value),
- updatedTimeList[index][2]
- ]
+ updatedTimeList[index][1] = parseInt(value) || 0
setTimeList(updatedTimeList)
}
const handlePlayerWonChange = (index, value) => {
const updatedTimeList = [...timeList]
- updatedTimeList[index] = [
- updatedTimeList[index][0],
- updatedTimeList[index][1],
- value
- ]
+ updatedTimeList[index][2] = value
setTimeList(updatedTimeList)
}
@@ -220,11 +232,13 @@ export default function TagMatch() {
const handleMillisecondsChange = (value) => {
const milliseconds = parseInt(value)
- videoObject.seekTo(milliseconds / 1000, true)
+ if (!isNaN(milliseconds)) {
+ videoObject.seekTo(milliseconds / 1000, true)
+ }
}
const handleRemoveTime = (index) => {
- const updatedTimeList = [...timeList].filter((item, i) => i !== index)
+ const updatedTimeList = [...timeList].filter((_, i) => i !== index)
setTimeList(updatedTimeList)
}
@@ -246,8 +260,10 @@ export default function TagMatch() {
const handleCopyColumns = () => {
const columns = [
- 'Index,Start Time,End Time',
- ...timeList.map((pair, index) => `${index + 1},${pair[0]},${pair[1]}`)
+ 'Index,Start Time,End Time,Point Winner',
+ ...timeList.map(
+ (pair, index) => `${index + 1},${pair[0]},${pair[1]},${pair[2]}`
+ )
].join('\n')
navigator.clipboard.writeText(columns)
}
@@ -276,39 +292,151 @@ export default function TagMatch() {
}
}, [videoObject])
+ // Updated handleCourtClick function remains unchanged
+ const handleCourtClick = (event) => {
+ if (!videoObject) return
+
+ const rect = event.currentTarget.getBoundingClientRect()
+ const widthOfCourt = rect.width
+ const heightOfCourt = rect.height
+
+ // Assuming the court width in inches is 432 (36 feet)
+ const courtWidthInInches = 432
+ const courtHeightInInches = 780 // 65 feet (common tennis court length)
+
+ // Calculate the scale ratios
+ const xRatio = courtWidthInInches / widthOfCourt
+ const yRatio = courtHeightInInches / heightOfCourt
+
+ // Calculate click position relative to the SVG container
+ const x = event.clientX - rect.left
+ const y = event.clientY - rect.top
+
+ // Convert to inches
+ const xInches = Math.round(x * xRatio)
+ const yInches = Math.round(y * yRatio)
+
+ // Determine the quadrant
+ const midX = courtWidthInInches / 2
+ const midY = courtHeightInInches / 2
+ let quadrant = ''
+
+ if (xInches < midX && yInches < midY) quadrant = 'Top-Left'
+ else if (xInches >= midX && yInches < midY) quadrant = 'Top-Right'
+ else if (xInches < midX && yInches >= midY) quadrant = 'Bottom-Left'
+ else if (xInches >= midX && yInches >= midY) quadrant = 'Bottom-Right'
+
+ // Update the state to display in the separate box
+ setClickedQuadrant(quadrant)
+ setClickedCoordinates({ x: xInches, y: yInches })
+
+ // Removed adding coordinates to timeList
+ }
+
return (
Loading...}>
-
-
-
+
+ {/* Video Player and Timestamp Table */}
+
+
-
-
+
+
+
+
+ {/* Timestamp Table */}
+
+
+
+
+ Index |
+ Start Time (ms) |
+ End Time (ms) |
+ Point Winner |
+ Remove |
+
+
+
+
+
+ Current Timestamp
+ |
+
+ {timeList.length !== 0 &&
+ timeList.map((pair, index) => {
+ if (curTimeStart === pair[0]) {
+ return (
+
+ )
+ } else return null
+ })}
+
+
+
+
+ All Timestamps
+ |
+
+ {timeList.map((pair, index) => (
+
+ ))}
+
+
+
+
+
+ {/* Tennis Court SVG */}
+
+
-
-
-
-
+
+ {/* Controls Section */}
+
+
- Current Time: {timerValue}ms |
+
+ Current Time:
+ |
+ {timerValue} ms |
- Jump to: |
+
+ Jump to:
+ |
+ |
@@ -319,7 +447,7 @@ export default function TagMatch() {
onChange={(event) =>
handleMillisecondsChange(event.target.value)
}
- style={{ marginRight: '10px' }}
+ className={styles.inputField}
/>
|
ms |
@@ -331,11 +459,11 @@ export default function TagMatch() {
placeholder="Minutes"
value={Math.floor(timerValue / 60000)}
onChange={(event) => {
- const minutes = parseFloat(event.target.value)
+ const minutes = parseFloat(event.target.value) || 0
const seconds = (timerValue % 60000) / 1000
handleMinutesSecondsChange(minutes, seconds)
}}
- style={{ marginRight: '10px' }}
+ className={styles.inputField}
/>
minutes |
@@ -345,69 +473,47 @@ export default function TagMatch() {
placeholder="Seconds"
value={Math.round((timerValue % 60000) / 1000)}
onChange={(event) => {
- const seconds = parseFloat(event.target.value)
+ const seconds = parseFloat(event.target.value) || 0
const minutes = Math.floor(timerValue / 60000)
handleMinutesSecondsChange(minutes, seconds)
}}
+ className={styles.inputField}
/>
seconds |
+
+
+ {/* Display Clicked Quadrant and Coordinates Under KeybindingsTable */}
+
+ {clickedQuadrant && clickedCoordinates ? (
+
+
Clicked Point Details
+
+ Quadrant: {clickedQuadrant}
+
+
+ Coordinates: (X: {clickedCoordinates.x}{' '}
+ inches, Y: {clickedCoordinates.y} inches)
+
+
+ ) : (
+
+
Clicked Point Details
+
Click on the court to see details here.
+
+ )}
+
-
-
-
-
- Index |
- Start Time |
- End Time |
- Point Winner |
- Remove |
-
-
-
-
- Current Timestamp |
-
- {timeList.length !== 0 &&
- timeList.map((pair, index) => {
- if (curTimeStart === pair[0]) {
- return (
-
- )
- } else return null
- })}
-
-
-
- All Timestamps |
-
- {timeList.map((pair, index) => (
-
- ))}
-
-
+ {/* Horizontal Line */}
+
+
+ {/* Note: Timestamp Table has been moved inside videoSection */}
)