Upload folder using huggingface_hub
Browse files- client/src/pages/WeeklyPractice.tsx +196 -266
client/src/pages/WeeklyPractice.tsx
CHANGED
|
@@ -1769,273 +1769,203 @@ const WeeklyPractice: React.FC = () => {
|
|
| 1769 |
</div>
|
| 1770 |
) : null}
|
| 1771 |
{((localStorage.getItem('viewMode')||'auto') !== 'student' && (JSON.parse(localStorage.getItem('user')||'{}').role === 'admin') && showSubmissions) && (
|
| 1772 |
-
|
| 1773 |
-
<div className="mb-4">
|
| 1774 |
-
<h3 className="text-xl font-bold text-gray-900 mb-2">{videoInfo.title}</h3>
|
| 1775 |
-
</div>
|
| 1776 |
-
|
| 1777 |
-
{/* Video Player */}
|
| 1778 |
-
<div className="bg-black rounded-lg aspect-video overflow-hidden relative">
|
| 1779 |
-
<video
|
| 1780 |
-
className="w-full h-full"
|
| 1781 |
-
controls
|
| 1782 |
-
preload="metadata"
|
| 1783 |
-
id="nike-video"
|
| 1784 |
-
onError={(e) => console.error('Video loading error:', e)}
|
| 1785 |
-
onLoadStart={() => console.log('Video loading started')}
|
| 1786 |
-
onCanPlay={() => console.log('Video can play')}
|
| 1787 |
-
>
|
| 1788 |
-
<source src="https://huggingface.co/spaces/linguabot/transcreation-frontend/resolve/main/public/videos/nike-winning-isnt-for-everyone.mp4" type="video/mp4" />
|
| 1789 |
-
Your browser does not support the video tag.
|
| 1790 |
-
</video>
|
| 1791 |
-
|
| 1792 |
-
{/* On-screen subtitles */}
|
| 1793 |
-
{currentDisplayedSubtitle && (
|
| 1794 |
-
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-10">
|
| 1795 |
-
<div className="bg-black bg-opacity-80 text-white px-6 py-3 rounded-lg text-center max-w-lg">
|
| 1796 |
-
<p className={`text-base font-medium leading-relaxed tracking-wide ${currentDisplayedSubtitle.length <= 42 ? 'whitespace-nowrap' : 'whitespace-pre-line'}`} style={{ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }}>
|
| 1797 |
-
{formatSubtitleForDisplay(currentDisplayedSubtitle)}
|
| 1798 |
-
</p>
|
| 1799 |
-
</div>
|
| 1800 |
-
</div>
|
| 1801 |
-
)}
|
| 1802 |
-
</div>
|
| 1803 |
-
</div>
|
| 1804 |
-
|
| 1805 |
-
{/* Right Panel - Translation Workspace */}
|
| 1806 |
<div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
|
| 1807 |
-
|
| 1808 |
-
|
| 1809 |
-
|
| 1810 |
-
|
| 1811 |
-
|
| 1812 |
-
|
| 1813 |
-
|
| 1814 |
-
|
| 1815 |
-
|
| 1816 |
-
|
| 1817 |
-
|
| 1818 |
-
|
| 1819 |
-
|
| 1820 |
-
|
| 1821 |
-
|
| 1822 |
-
|
| 1823 |
-
|
| 1824 |
-
|
| 1825 |
-
|
| 1826 |
-
|
| 1827 |
-
|
| 1828 |
-
|
| 1829 |
-
|
| 1830 |
-
|
| 1831 |
-
|
| 1832 |
-
|
| 1833 |
-
|
| 1834 |
-
|
| 1835 |
-
|
| 1836 |
-
|
| 1837 |
-
|
| 1838 |
-
|
| 1839 |
-
|
| 1840 |
-
|
| 1841 |
-
|
| 1842 |
-
|
| 1843 |
-
|
| 1844 |
-
|
| 1845 |
-
|
| 1846 |
-
|
| 1847 |
-
|
| 1848 |
-
|
| 1849 |
-
|
| 1850 |
-
|
| 1851 |
-
|
| 1852 |
-
|
| 1853 |
-
|
| 1854 |
-
|
| 1855 |
-
|
| 1856 |
-
|
| 1857 |
-
|
| 1858 |
-
|
| 1859 |
-
|
| 1860 |
-
|
| 1861 |
-
|
| 1862 |
-
|
| 1863 |
-
|
| 1864 |
-
|
| 1865 |
-
|
| 1866 |
-
|
| 1867 |
-
|
| 1868 |
-
|
| 1869 |
-
|
| 1870 |
-
|
| 1871 |
-
|
| 1872 |
-
|
| 1873 |
-
|
| 1874 |
-
|
| 1875 |
-
|
| 1876 |
-
|
| 1877 |
-
|
| 1878 |
-
|
| 1879 |
-
|
| 1880 |
-
|
| 1881 |
-
|
| 1882 |
-
|
| 1883 |
-
|
| 1884 |
-
|
| 1885 |
-
|
| 1886 |
-
|
| 1887 |
-
|
| 1888 |
-
|
| 1889 |
-
|
| 1890 |
-
|
| 1891 |
-
|
| 1892 |
-
|
| 1893 |
-
|
| 1894 |
-
|
| 1895 |
-
|
| 1896 |
-
|
| 1897 |
-
|
| 1898 |
-
|
| 1899 |
-
|
| 1900 |
-
|
| 1901 |
-
|
| 1902 |
-
|
| 1903 |
-
|
| 1904 |
-
|
| 1905 |
-
|
| 1906 |
-
|
| 1907 |
-
|
| 1908 |
-
|
| 1909 |
-
|
| 1910 |
-
|
| 1911 |
-
|
| 1912 |
-
|
| 1913 |
-
|
| 1914 |
-
|
| 1915 |
-
|
| 1916 |
-
|
| 1917 |
-
|
| 1918 |
-
|
| 1919 |
-
|
| 1920 |
-
|
| 1921 |
-
|
| 1922 |
-
|
| 1923 |
-
|
| 1924 |
-
|
| 1925 |
-
|
| 1926 |
-
|
| 1927 |
-
|
| 1928 |
-
|
| 1929 |
-
|
| 1930 |
-
|
| 1931 |
-
|
| 1932 |
-
|
| 1933 |
-
|
| 1934 |
-
|
| 1935 |
-
|
| 1936 |
-
|
| 1937 |
-
|
| 1938 |
-
|
| 1939 |
-
|
| 1940 |
-
|
| 1941 |
-
|
| 1942 |
-
|
| 1943 |
-
|
| 1944 |
-
|
| 1945 |
-
|
| 1946 |
-
|
| 1947 |
-
|
| 1948 |
-
|
| 1949 |
-
|
| 1950 |
-
|
| 1951 |
-
|
| 1952 |
-
|
| 1953 |
-
|
| 1954 |
-
|
| 1955 |
-
|
| 1956 |
-
|
| 1957 |
-
|
| 1958 |
-
|
| 1959 |
-
|
| 1960 |
-
|
| 1961 |
-
|
| 1962 |
-
|
| 1963 |
-
|
| 1964 |
-
|
| 1965 |
-
|
| 1966 |
-
|
| 1967 |
-
|
| 1968 |
-
|
| 1969 |
-
|
| 1970 |
-
|
| 1971 |
-
|
| 1972 |
-
|
| 1973 |
-
|
| 1974 |
-
|
| 1975 |
-
|
| 1976 |
-
|
| 1977 |
-
|
| 1978 |
-
|
| 1979 |
-
|
| 1980 |
-
|
| 1981 |
-
|
| 1982 |
-
|
| 1983 |
-
|
| 1984 |
-
|
| 1985 |
-
|
| 1986 |
-
|
| 1987 |
-
|
| 1988 |
-
|
| 1989 |
-
|
| 1990 |
-
|
| 1991 |
-
|
| 1992 |
-
|
| 1993 |
-
|
| 1994 |
-
|
| 1995 |
-
|
| 1996 |
-
|
| 1997 |
-
|
| 1998 |
-
|
| 1999 |
-
|
| 2000 |
-
|
| 2001 |
-
|
| 2002 |
-
<span className="font-medium text-gray-900">
|
| 2003 |
-
{submission.username}
|
| 2004 |
-
</span>
|
| 2005 |
-
<div className="flex items-center space-x-2">
|
| 2006 |
-
<span className="text-xs text-gray-500">
|
| 2007 |
-
{new Date(submission.submissionDate).toLocaleString()}
|
| 2008 |
-
</span>
|
| 2009 |
-
{((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin') && (
|
| 2010 |
-
<button
|
| 2011 |
-
className="px-2 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600"
|
| 2012 |
-
onClick={async () => {
|
| 2013 |
-
try {
|
| 2014 |
-
await api.delete(`/api/subtitles/${submission._id}`);
|
| 2015 |
-
await fetchUserSubmissions(weeklyPractice);
|
| 2016 |
-
} catch (e) {
|
| 2017 |
-
console.error('Delete subtitle submission failed', e);
|
| 2018 |
-
}
|
| 2019 |
-
}}
|
| 2020 |
-
>
|
| 2021 |
-
Delete
|
| 2022 |
-
</button>
|
| 2023 |
-
)}
|
| 2024 |
-
</div>
|
| 2025 |
-
</div>
|
| 2026 |
-
<div className="text-sm text-gray-800 whitespace-pre-wrap">{submission.chineseTranslation}</div>
|
| 2027 |
-
</div>
|
| 2028 |
-
))}
|
| 2029 |
-
</div>
|
| 2030 |
-
</>
|
| 2031 |
-
) : (
|
| 2032 |
-
<div className="text-gray-600">No submissions yet.</div>
|
| 2033 |
-
)}
|
| 2034 |
-
</div>
|
| 2035 |
-
)}
|
| 2036 |
-
</div>
|
| 2037 |
-
</div>
|
| 2038 |
-
)}
|
| 2039 |
</div>
|
| 2040 |
) : (
|
| 2041 |
/* Weekly Practice */
|
|
|
|
| 1769 |
</div>
|
| 1770 |
) : null}
|
| 1771 |
{((localStorage.getItem('viewMode')||'auto') !== 'student' && (JSON.parse(localStorage.getItem('user')||'{}').role === 'admin') && showSubmissions) && (
|
| 1772 |
+
<>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1773 |
<div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
|
| 1774 |
+
<div className="mb-4">
|
| 1775 |
+
<h3 className="text-xl font-bold text-gray-900 mb-2">{videoInfo.title}</h3>
|
| 1776 |
+
</div>
|
| 1777 |
+
|
| 1778 |
+
{/* Video Player */}
|
| 1779 |
+
<div className="bg-black rounded-lg aspect-video overflow-hidden relative">
|
| 1780 |
+
<video
|
| 1781 |
+
className="w-full h-full"
|
| 1782 |
+
controls
|
| 1783 |
+
preload="metadata"
|
| 1784 |
+
id="nike-video"
|
| 1785 |
+
onError={(e) => console.error('Video loading error:', e)}
|
| 1786 |
+
onLoadStart={() => console.log('Video loading started')}
|
| 1787 |
+
onCanPlay={() => console.log('Video can play')}
|
| 1788 |
+
>
|
| 1789 |
+
<source src="https://huggingface.co/spaces/linguabot/transcreation-frontend/resolve/main/public/videos/nike-winning-isnt-for-everyone.mp4" type="video/mp4" />
|
| 1790 |
+
Your browser does not support the video tag.
|
| 1791 |
+
</video>
|
| 1792 |
+
|
| 1793 |
+
{/* On-screen subtitles */}
|
| 1794 |
+
{currentDisplayedSubtitle && (
|
| 1795 |
+
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-10">
|
| 1796 |
+
<div className="bg-black bg-opacity-80 text-white px-6 py-3 rounded-lg text-center max-w-lg">
|
| 1797 |
+
<p className={`text-base font-medium leading-relaxed tracking-wide ${currentDisplayedSubtitle.length <= 42 ? 'whitespace-nowrap' : 'whitespace-pre-line'}`} style={{ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }}>
|
| 1798 |
+
{formatSubtitleForDisplay(currentDisplayedSubtitle)}
|
| 1799 |
+
</p>
|
| 1800 |
+
</div>
|
| 1801 |
+
</div>
|
| 1802 |
+
)}
|
| 1803 |
+
</div>
|
| 1804 |
+
</div>
|
| 1805 |
+
|
| 1806 |
+
{/* Right Panel - Translation Workspace */}
|
| 1807 |
+
<div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
|
| 1808 |
+
<h3 className="text-xl font-bold text-gray-900 mb-4">Translation Workspace</h3>
|
| 1809 |
+
|
| 1810 |
+
{/* Segment Navigation Grid */}
|
| 1811 |
+
{loadingSubtitles ? (
|
| 1812 |
+
<div className="grid grid-cols-9 gap-1 mb-6">
|
| 1813 |
+
{Array.from({ length: 26 }, (_, i) => (
|
| 1814 |
+
<div key={i} className="px-1 py-1 rounded text-xs w-full bg-gray-200 animate-pulse">
|
| 1815 |
+
{i + 1}
|
| 1816 |
+
</div>
|
| 1817 |
+
))}
|
| 1818 |
+
</div>
|
| 1819 |
+
) : (
|
| 1820 |
+
<div className="grid grid-cols-9 gap-1 mb-6">
|
| 1821 |
+
{subtitleSegments.map((segment) => (
|
| 1822 |
+
<button
|
| 1823 |
+
key={segment.id}
|
| 1824 |
+
onClick={() => handleSegmentClick(segment.id)}
|
| 1825 |
+
className={`px-1 py-1 rounded text-xs w-full ${getSegmentButtonClass(segment.id)}`}
|
| 1826 |
+
>
|
| 1827 |
+
{segment.id}
|
| 1828 |
+
</button>
|
| 1829 |
+
))}
|
| 1830 |
+
</div>
|
| 1831 |
+
)}
|
| 1832 |
+
|
| 1833 |
+
{/* Admin Time Code Editor */}
|
| 1834 |
+
{editingSegment && (((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin')) && (
|
| 1835 |
+
<div className="bg-indigo-50 border border-indigo-200 rounded-lg p-4 mb-4">
|
| 1836 |
+
<h4 className="text-sm font-medium text-indigo-800 mb-2">Edit Time Codes for Segment {editingSegment}</h4>
|
| 1837 |
+
<div className="grid grid-cols-2 gap-2">
|
| 1838 |
+
<div>
|
| 1839 |
+
<label className="block text-xs text-indigo-700 mb-1">Start Time</label>
|
| 1840 |
+
<input
|
| 1841 |
+
type="text"
|
| 1842 |
+
id="startTimeInput"
|
| 1843 |
+
defaultValue={subtitleSegments[editingSegment - 1]?.startTime}
|
| 1844 |
+
className="w-full px-2 py-1 text-xs border border-indigo-300 rounded"
|
| 1845 |
+
placeholder="00:00:00,000"
|
| 1846 |
+
/>
|
| 1847 |
+
</div>
|
| 1848 |
+
<div>
|
| 1849 |
+
<label className="block text-xs text-indigo-700 mb-1">End Time</label>
|
| 1850 |
+
<input
|
| 1851 |
+
type="text"
|
| 1852 |
+
id="endTimeInput"
|
| 1853 |
+
defaultValue={subtitleSegments[editingSegment - 1]?.endTime}
|
| 1854 |
+
className="w-full px-2 py-1 text-xs border border-indigo-300 rounded"
|
| 1855 |
+
placeholder="00:00:00,000"
|
| 1856 |
+
/>
|
| 1857 |
+
</div>
|
| 1858 |
+
</div>
|
| 1859 |
+
<div className="flex space-x-2 mt-2">
|
| 1860 |
+
<button
|
| 1861 |
+
onClick={() => setEditingSegment(null)}
|
| 1862 |
+
className="px-2 py-1 text-xs bg-gray-500 text-white rounded"
|
| 1863 |
+
>
|
| 1864 |
+
Cancel
|
| 1865 |
+
</button>
|
| 1866 |
+
<button
|
| 1867 |
+
onClick={() => {
|
| 1868 |
+
const startInput = document.getElementById('startTimeInput') as HTMLInputElement;
|
| 1869 |
+
const endInput = document.getElementById('endTimeInput') as HTMLInputElement;
|
| 1870 |
+
if (startInput && endInput && startInput.value && endInput.value) {
|
| 1871 |
+
handleSaveTimeCode(editingSegment, startInput.value, endInput.value);
|
| 1872 |
+
}
|
| 1873 |
+
}}
|
| 1874 |
+
className="px-2 py-1 text-xs bg-indigo-600 text-white rounded"
|
| 1875 |
+
>
|
| 1876 |
+
Save
|
| 1877 |
+
</button>
|
| 1878 |
+
</div>
|
| 1879 |
+
</div>
|
| 1880 |
+
)}
|
| 1881 |
+
|
| 1882 |
+
{/* Current Segment Details */}
|
| 1883 |
+
<div className="bg-gray-50 rounded-lg p-4 mb-4 border border-gray-200">
|
| 1884 |
+
<div className="flex items-center justify-between">
|
| 1885 |
+
<p className="text-gray-800">
|
| 1886 |
+
Segment {currentSegment}: {subtitleSegments[currentSegment - 1]?.startTime} – {subtitleSegments[currentSegment - 1]?.endTime} ({subtitleSegments[currentSegment - 1]?.duration})
|
| 1887 |
+
</p>
|
| 1888 |
+
{((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin') && (
|
| 1889 |
+
<button
|
| 1890 |
+
onClick={() => handleEditTimeCode(currentSegment)}
|
| 1891 |
+
className="bg-indigo-600 hover:bg-indigo-700 text-white px-2 py-1 rounded text-xs flex items-center space-x-1"
|
| 1892 |
+
title="Edit time codes"
|
| 1893 |
+
>
|
| 1894 |
+
<PencilIcon className="w-3 h-3" />
|
| 1895 |
+
<span>Edit</span>
|
| 1896 |
+
</button>
|
| 1897 |
+
)}
|
| 1898 |
+
</div>
|
| 1899 |
+
</div>
|
| 1900 |
+
|
| 1901 |
+
{/* Source Text */}
|
| 1902 |
+
<div className="mb-4">
|
| 1903 |
+
<label className="block text-sm font-medium text-gray-700 mb-2">Source Text</label>
|
| 1904 |
+
<textarea
|
| 1905 |
+
value={subtitleText}
|
| 1906 |
+
readOnly
|
| 1907 |
+
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-gray-800"
|
| 1908 |
+
rows={2}
|
| 1909 |
+
/>
|
| 1910 |
+
</div>
|
| 1911 |
+
|
| 1912 |
+
{/* Target Text */}
|
| 1913 |
+
<div className="mb-4">
|
| 1914 |
+
<label className="block text-sm font-medium text-gray-700 mb-2">Target Text</label>
|
| 1915 |
+
<textarea
|
| 1916 |
+
value={targetText}
|
| 1917 |
+
onChange={(e) => handleTargetTextChange(e.target.value)}
|
| 1918 |
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
| 1919 |
+
rows={2}
|
| 1920 |
+
placeholder="Enter your translation..."
|
| 1921 |
+
/>
|
| 1922 |
+
<div className="flex justify-between items-center mt-2">
|
| 1923 |
+
<span className="text-xs text-gray-600">Recommended: 2 lines max, 16 chars/line (Netflix standard)</span>
|
| 1924 |
+
<span className="text-xs text-gray-600">{characterCount} characters</span>
|
| 1925 |
+
</div>
|
| 1926 |
+
</div>
|
| 1927 |
+
|
| 1928 |
+
{/* Action Buttons */}
|
| 1929 |
+
<div className="flex space-x-3 mb-4">
|
| 1930 |
+
<button
|
| 1931 |
+
onClick={handleSaveTranslation}
|
| 1932 |
+
className={`px-4 py-2 rounded-lg ${
|
| 1933 |
+
saveStatus === 'Saved!' ? 'bg-green-600 hover:bg-green-700' :
|
| 1934 |
+
saveStatus === 'Saved Locally' ? 'bg-yellow-600 hover:bg-yellow-700' :
|
| 1935 |
+
saveStatus === 'Error' ? 'bg-red-600 hover:bg-red-700' :
|
| 1936 |
+
'bg-indigo-600 hover:bg-indigo-700'
|
| 1937 |
+
} text-white`}
|
| 1938 |
+
>
|
| 1939 |
+
{saveStatus}
|
| 1940 |
+
</button>
|
| 1941 |
+
<button
|
| 1942 |
+
onClick={handlePreviewTranslation}
|
| 1943 |
+
className={`px-4 py-2 rounded-lg flex items-center space-x-2 ${
|
| 1944 |
+
showTranslatedSubtitles
|
| 1945 |
+
? 'bg-green-500 hover:bg-green-600 text-white'
|
| 1946 |
+
: 'bg-gray-500 hover:bg-gray-600 text-white'
|
| 1947 |
+
}`}
|
| 1948 |
+
>
|
| 1949 |
+
<span>{showTranslatedSubtitles ? 'Show Original' : 'Show Translation'}</span>
|
| 1950 |
+
</button>
|
| 1951 |
+
</div>
|
| 1952 |
+
|
| 1953 |
+
{/* Translation Progress */}
|
| 1954 |
+
<div className="mb-4">
|
| 1955 |
+
<div className="flex justify-between items-center mb-2">
|
| 1956 |
+
<span className="text-sm font-medium text-gray-700">Translation Progress</span>
|
| 1957 |
+
<span className="text-sm text-gray-600">{Object.keys(subtitleTranslations).length} of {subtitleSegments.length} segments completed</span>
|
| 1958 |
+
</div>
|
| 1959 |
+
<div className="w-full bg-gray-200 rounded-full h-2">
|
| 1960 |
+
<div
|
| 1961 |
+
className="bg-green-500 h-2 rounded-full transition-all duration-300"
|
| 1962 |
+
style={{ width: `${(Object.keys(subtitleTranslations).length / subtitleSegments.length) * 100}%` }}
|
| 1963 |
+
></div>
|
| 1964 |
+
</div>
|
| 1965 |
+
</div>
|
| 1966 |
+
</div>
|
| 1967 |
+
</>
|
| 1968 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1969 |
</div>
|
| 1970 |
) : (
|
| 1971 |
/* Weekly Practice */
|