Skip to content

Commit

Permalink
Merge pull request #134 from awest25/court-svg
Browse files Browse the repository at this point in the history
The tennis court graphic in the tagging page is now an SVG
  • Loading branch information
awest25 authored May 13, 2024
2 parents aa4130d + cdeec92 commit 76116bd
Show file tree
Hide file tree
Showing 6 changed files with 2,973 additions and 2,273 deletions.
111 changes: 67 additions & 44 deletions app/(interactive)/tag-match/[slug]/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import styles from '../../../styles/TagMatch.module.css';
import { usePathname } from 'next/navigation'
import getMatchInfo from '../../../services/getMatchInfo.js';
import updateMatchDocument from '../../../services/updateMatchDocument.js';
import TennisCourtSVG from '@/app/components/TennisCourtSVG';

export default function TagMatch() {
// const router = useRouter();
Expand All @@ -20,6 +21,8 @@ export default function TagMatch() {
const [currentPage, setCurrentPage] = useState('ServerName'); // TODO: the default should continue from what was filled in last
const [taggerHistory, setTaggerHistory] = useState([]); // Array to hold the history of states
const [isPublished, setIsPublished] = useState(false); // TODO: impliment this functionality (only show published matches)
const [matchMetadata, setMatchMetadata] = useState({});

const [popUp, setPopUp] = useState([]);
const [isVisible, setIsVisible] = useState(false);
const [displayPopUp, setDisplayPopUp] = useState(false);
Expand Down Expand Up @@ -48,6 +51,10 @@ export default function TagMatch() {
return { ...oldTableState, rows: [] };
});
}

// Set the metadata to matchDocument but without the 'points'
const { points, ...metadata } = matchDocument;
setMatchMetadata(metadata);
});
}, [matchId]);

Expand Down Expand Up @@ -147,27 +154,27 @@ export default function TagMatch() {
}

const addNewRowAndSync = () => {
pullAndPushRows();
setTableState(oldTableState => {
pullAndPushRows(oldTableState.rows, null);

let newTimestamp = getVideoTimestamp();
let newTimestamp = getVideoTimestamp();

// Create a new row object with the required structure
const newRow = columnNames.reduce((acc, columnName) => {
// Check if a row already exists with the new timestamp
let existingRow = tableState.rows.find(row => row.pointStartTime === newTimestamp);
// Create a new row object with the required structure
const newRow = columnNames.reduce((acc, columnName) => {
// Check if a row already exists with the new timestamp
let existingRow = tableState.rows.find(row => row.pointStartTime === newTimestamp);

while (existingRow !== undefined) {
// If a row already exists, increment the timestamp by 1
newTimestamp += 1;
existingRow = tableState.rows.find(row => row.pointStartTime === newTimestamp);
}
while (existingRow !== undefined) {
// If a row already exists, increment the timestamp by 1
newTimestamp += 1;
existingRow = tableState.rows.find(row => row.pointStartTime === newTimestamp);
}

acc[columnName] = columnName === 'pointStartTime' ? newTimestamp : '';
return acc;
}, {});
acc[columnName] = columnName === 'pointStartTime' ? newTimestamp : '';
return acc;
}, {});

// Add new row and sort
setTableState(oldTableState => {
// Add new row and sort
const updatedTable = [...oldTableState.rows, newRow];
// Sort the table by 'pointStartTime'
updatedTable.sort((a, b) => a.pointStartTime - b.pointStartTime);
Expand All @@ -188,7 +195,7 @@ export default function TagMatch() {
const newActiveRowIndex = rowIndex === oldTableState.activeRowIndex ? oldTableState.activeRowIndex - 1 : oldTableState.activeRowIndex;
return { rows: updatedTable, activeRowIndex: newActiveRowIndex };
});
pullAndPushRows(rowToDeleteTimestamp);
pullAndPushRows(tableState.rows, rowToDeleteTimestamp);
}


Expand Down Expand Up @@ -249,9 +256,9 @@ export default function TagMatch() {
}, [displayPopUp]);


const pullAndPushRows = async (rowToDeleteTimestamp = null) => {
const pullAndPushRows = async (rowState, rowToDeleteTimestamp = null) => {
try {
const tableSnapshot = [...tableState.rows]; // Snapshot of the table before fetching updates
const tableSnapshot = [...rowState]; // Snapshot of the table before fetching updates
// Fetch the current document state from the database
const matchDocument = await getMatchInfo(matchId);
const incomingRows = matchDocument.points ?? [];
Expand Down Expand Up @@ -308,7 +315,7 @@ export default function TagMatch() {

// Toggle the publushed state of the match
const togglePublish = async () => {
pullAndPushRows();
pullAndPushRows(tableState.rows, null);
try {
await updateMatchDocument(matchId, {
published: !isPublished
Expand Down Expand Up @@ -352,26 +359,43 @@ export default function TagMatch() {
const buttonData = getTaggerButtonData(updateActiveRow, addNewRowAndSync, setCurrentPage);

const handleImageClick = (event) => {
console.log("event: ", event);
const courtWidthInInches = 432; // The court is 36 feet wide, or 432 inches
const courtHeightInInches = 936; // The court is 78 feet long, or 936 inches
// const courtHeightInInches = 936; // The court is 78 feet long, or 936 inches

// The current SVG has the actual in width of the court as 360 out of 600 total
// The height is 780 out of 1080 total
// This makes the ratio 0.6 for width and 0.7222 for height
const xRatio = 0.6;
// const yRatio = 0.7222;

// Get the bounding rectangle of the SVG container
const rect = event.currentTarget.getBoundingClientRect();

// Get the bounding rectangle of the target (image)
const rect = event.target.getBoundingClientRect();
const widthOfCourt = rect.width; // Using rect.width is more reliable
const heightOfCourt = rect.height;

const widthOfCourt = rect.right - rect.left;
const heightOfCourt = rect.bottom - rect.top;
const inchesPerPixel = courtWidthInInches / (widthOfCourt * xRatio); // This is slightly wrong bc it rounds at some point?

// Calculate the click position relative to the image
// Calculate the click position relative to the SVG container
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;

// Calculate the click position relative to the court
const xInches = Math.round((x / widthOfCourt) * courtWidthInInches);
const yInches = Math.round((y / heightOfCourt) * courtHeightInInches);
// Find how far from the center the click was
const xFromCenter = x - widthOfCourt / 2;
const yFromCenter = y - heightOfCourt / 2;

// Calculate the click position in inches
let xInches = Math.round(xFromCenter * inchesPerPixel);
let yInches = Math.round(yFromCenter * inchesPerPixel) * -1;

// Convert -0 to 0
xInches = Object.is(xInches, -0) ? 0 : xInches;
yInches = Object.is(yInches, -0) ? 0 : yInches;

console.log("xInches: " + xInches + " yInches: " + yInches);
return { 'x': xInches, 'y': yInches };
}
return { x: xInches, y: yInches };
};


return (
Expand All @@ -391,35 +415,34 @@ export default function TagMatch() {
<div>
<p>{currentPage}</p>
{buttonData[currentPage].map((button, index) => {
return button.courtImage === true ? (
return button.courtImage ? (
<div>
<p>{button.label}</p>
<img
src="/images/Tennis_Court_Full.png"
alt="tennis court"
onClick={(event) => {
<TennisCourtSVG className={styles.courtImage} courtType={button.courtImage} handleImageClick={(event) => {
setPopUp([])
saveToHistory();
let data = handleImageClick(event); // returns data.x and data.y coordinates
let data = matchMetadata;
// add data.x and data.y to the data object
const { x, y } = handleImageClick(event);
data.x = x;
data.y = y;
data.table = tableState.rows;
data.activeRowIndex = tableState.activeRowIndex;
data.videoTimestamp = getVideoTimestamp();
button.action(data);
showPopUp()
}}
style={{ width: "10%" }}
/>
showPopUp();
}} />
</div>
) : (
<button className={styles.customButton} key={index} onClick={() => {
setPopUp([])
saveToHistory();
let data = {};
let data = matchMetadata;
data.table = tableState.rows;
data.activeRowIndex = tableState.activeRowIndex;
data.videoTimestamp = getVideoTimestamp();
button.action(data);
showPopUp()
showPopUp();
}}>
{button.label}
</button>
Expand Down Expand Up @@ -465,7 +488,7 @@ export default function TagMatch() {
<td key={colIndex}>
<input
type="text"
value={row[columnName] || ''}
value={row[columnName] === undefined || row[columnName] === null ? '' : row[columnName]}
onChange={(event) => {
saveToHistory(); // Save the current state to history first
changeRowValue(rowIndex, columnName, event.target.value); // Then handle the change
Expand Down
134 changes: 134 additions & 0 deletions app/components/TennisCourtSVG.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React from 'react';

const TennisCourtSVG = ({ handleImageClick, courtType }) => {
return (
<svg onClick={handleImageClick} width="600" height="1080" viewBox="0 0 600 1080" style={{ border: '4px solid red', width: 'auto', height: '30rem' }}>
{/* Background */}
<rect x="0" y="540" width="600" height="540" fill="lightgray" />
<rect x="0" y="0" width="600" height="540" fill="lightgray" />

{/* Top half of the court */}
<rect x="120" y="150" width="45" height="390" fill="lightblue" />
<rect x="165" y="150" width="270" height="180" fill="lightblue" />
<rect x="435" y="150" width="45" height="390" fill="lightblue" />
<rect x="165" y="330" width="135" height="210" fill="lightblue" />
<rect x="300" y="330" width="135" height="210" fill="lightblue" />

{/* Bottom half of the court, mirrored from the top half */}
<rect x="120" y="540" width="45" height="390" fill="lightblue" />
<rect x="165" y="750" width="270" height="180" fill="lightblue" />
<rect x="435" y="540" width="45" height="390" fill="lightblue" />
<rect x="165" y="540" width="135" height="210" fill="lightblue" />
<rect x="300" y="540" width="135" height="210" fill="lightblue" />

{/* Optional service sections top half */}
{/* Optional service sections bottom half */}
{(courtType === 'serve') && (
<>
<rect x="165" y="330" width="45" height="210" fill="lightblue" />
<rect x="210" y="330" width="45" height="210" fill="lightblue" />
<rect x="255" y="330" width="45" height="210" fill="lightblue" />
<rect x="300" y="330" width="45" height="210" fill="lightblue" />
<rect x="345" y="330" width="45" height="210" fill="lightblue" />
<rect x="390" y="330" width="45" height="210" fill="lightblue" />

<rect x="165" y="540" width="45" height="210" fill="lightblue" />
<rect x="210" y="540" width="45" height="210" fill="lightblue" />
<rect x="255" y="540" width="45" height="210" fill="lightblue" />
<rect x="300" y="540" width="45" height="210" fill="lightblue" />
<rect x="345" y="540" width="45" height="210" fill="lightblue" />
<rect x="390" y="540" width="45" height="210" fill="lightblue" />
</>
)}

{/* T lines and center lines */}
<line x1="165" y1="330" x2="435" y2="330" stroke="white" strokeWidth="2" />
<line x1="165" y1="750" x2="435" y2="750" stroke="white" strokeWidth="2" />
<line x1="300" y1="330" x2="300" y2="540" stroke="white" strokeWidth="2" />
<line x1="300" y1="540" x2="300" y2="750" stroke="white" strokeWidth="2" />

{/* Alley lines */}
<line x1="165" y1="150" x2="165" y2="540" stroke="white" strokeWidth="2" />
<line x1="435" y1="150" x2="435" y2="540" stroke="white" strokeWidth="2" />
<line x1="165" y1="540" x2="165" y2="930" stroke="white" strokeWidth="2" />
<line x1="435" y1="540" x2="435" y2="930" stroke="white" strokeWidth="2" />

{/* Outside alley lines */}
<line x1="120" y1="150" x2="120" y2="540" stroke="white" strokeWidth="2" />
<line x1="480" y1="150" x2="480" y2="540" stroke="white" strokeWidth="2" />
<line x1="120" y1="930" x2="120" y2="540" stroke="white" strokeWidth="2" />
<line x1="480" y1="930" x2="480" y2="540" stroke="white" strokeWidth="2" />

{/* Top and Bottom lines */}
<line x1="120" y1="150" x2="480" y2="150" stroke="white" strokeWidth="2" />
<line x1="120" y1="930" x2="480" y2="930" stroke="white" strokeWidth="2" />

{/* Net Line */}
<line className="gray-hoverable" x1="90" y1="540" x2="510" y2="540" stroke="black" strokeWidth="4" />

<style>
{`
rect:hover { fill: green; }
.gray-hoverable:hover { stroke: darkgray; }
`}
</style>
</svg>
);
}

export default TennisCourtSVG;


/*
* HTML version
*/

/*
<svg width="480" height="900" style="border: 4px solid red;">
<!-- Background out -->
<rect x="0" y="450" width="480" height="450" fill="lightgray" /> <!-- Top background -->
<rect x="0" y="0" width="480" height="450" fill="lightgray" /> <!-- Bottom background -->
<!-- Top half of the court -->
<rect x="60" y="60" width="45" height="390" fill="lightblue" /> <!-- Top left alley -->
<rect x="105" y="60" width="270" height="180" fill="lightblue" /> <!-- Top upper middle -->
<rect x="375" y="60" width="45" height="390" fill="lightblue" /> <!-- Top right alley -->
<rect x="105" y="240" width="135" height="210" fill="lightblue" /> <!-- Top left middle -->
<rect x="240" y="240" width="135" height="210" fill="lightblue" /> <!-- Top right middle -->
<!-- Bottom half of the court, mirrored from the top half -->
<rect x="60" y="450" width="45" height="390" fill="lightblue" /> <!-- Bottom left alley -->
<rect x="105" y="660" width="270" height="180" fill="lightblue" /> <!-- Bottom upper middle -->
<rect x="375" y="450" width="45" height="390" fill="lightblue" /> <!-- Bottom right alley -->
<rect x="105" y="450" width="135" height="210" fill="lightblue" /> <!-- Bottom left middle -->
<rect x="240" y="450" width="135" height="210" fill="lightblue" /> <!-- Bottom right middle -->
<!-- T lines -->
<line x1="105" y1="240" x2="375" y2="240" stroke="white" stroke-width="2" />
<line x1="105" y1="660" x2="375" y2="660" stroke="white" stroke-width="2" /> <!-- Mirrored T line -->
<line x1="240" y1="240" x2="240" y2="450" stroke="white" stroke-width="2" />
<line x1="240" y1="450" x2="240" y2="660" stroke="white" stroke-width="2" /> <!-- Mirrored center line -->
<!-- Alley lines -->
<line x1="105" y1="60" x2="105" y2="450" stroke="white" stroke-width="2" />
<line x1="375" y1="60" x2="375" y2="450" stroke="white" stroke-width="2" />
<line x1="105" y1="450" x2="105" y2="840" stroke="white" stroke-width="2" /> <!-- Mirrored alley line -->
<line x1="375" y1="450" x2="375" y2="840" stroke="white" stroke-width="2" /> <!-- Mirrored alley line -->
<line x1="60" y1="60" x2="60" y2="450" stroke="white" stroke-width="2" /> <!-- Top left outside alley -->
<line x1="420" y1="60" x2="420" y2="450" stroke="white" stroke-width="2" /> <!-- Top right outside alley -->
<line x1="60" y1="840" x2="60" y2="450" stroke="white" stroke-width="2" /> <!-- Bottom left outside alley -->
<line x1="420" y1="840" x2="420" y2="450" stroke="white" stroke-width="2" /> <!-- Bottom right outside alley -->
<!-- Top and Bottom lines -->
<line x1="60" y1="60" x2="420" y2="60" stroke="white" stroke-width="2" />
<line x1="60" y1="840" x2="420" y2="840" stroke="white" stroke-width="2" />
<!-- Net Line -->
<line class="gray-hoverable" x1="30" y1="450" x2="450" y2="450" stroke="black" stroke-width="4" />
<style>
rect:hover { fill: green; }
.gray-hoverable:hover { fill: darkgray; stroke: darkgray; }
</style>
</svg>
*/
Loading

0 comments on commit 76116bd

Please sign in to comment.