Upload folder using huggingface_hub
Browse files
client/src/pages/TutorialTasks.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
import React, { useState, useEffect, useCallback, useRef, useLayoutEffect } from 'react';
|
| 2 |
-
import { flushSync } from 'react-dom';
|
| 3 |
import { useNavigate } from 'react-router-dom';
|
| 4 |
import { api } from '../services/api';
|
| 5 |
import {
|
|
@@ -868,36 +867,13 @@ const TutorialTasks: React.FC = () => {
|
|
| 868 |
setSubmitting({ ...submitting, [taskId]: true });
|
| 869 |
});
|
| 870 |
const user = JSON.parse(localStorage.getItem('user') || '{}');
|
| 871 |
-
// Safari: build the optimistic item synchronously so height stays stable; do network after paint
|
| 872 |
-
let optimisticId: string | null = null;
|
| 873 |
-
const userName = user.name || 'Unknown';
|
| 874 |
-
flushSync(() => {
|
| 875 |
-
const temp = {
|
| 876 |
-
_id: `optimistic-${Date.now()}`,
|
| 877 |
-
sourceTextId: taskId,
|
| 878 |
-
transcreation: text,
|
| 879 |
-
groupNumber: group,
|
| 880 |
-
culturalAdaptations: [],
|
| 881 |
-
username: userName,
|
| 882 |
-
status: 'pending',
|
| 883 |
-
isOwner: true,
|
| 884 |
-
createdAt: new Date().toISOString()
|
| 885 |
-
} as any;
|
| 886 |
-
optimisticId = temp._id;
|
| 887 |
-
setUserSubmissions(prev => {
|
| 888 |
-
const current = prev[taskId] || [];
|
| 889 |
-
const next = [temp, ...current];
|
| 890 |
-
return { ...prev, [taskId]: next };
|
| 891 |
-
});
|
| 892 |
-
});
|
| 893 |
-
|
| 894 |
const response = await new Promise((resolve) => requestAnimationFrame(async () => {
|
| 895 |
const res = await api.post('/api/submissions', {
|
| 896 |
sourceTextId: taskId,
|
| 897 |
transcreation: text,
|
| 898 |
groupNumber: group,
|
| 899 |
culturalAdaptations: [],
|
| 900 |
-
username:
|
| 901 |
});
|
| 902 |
resolve(res);
|
| 903 |
})) as any;
|
|
@@ -906,44 +882,34 @@ const TutorialTasks: React.FC = () => {
|
|
| 906 |
const created = response.data;
|
| 907 |
console.log('Submission created successfully:', created);
|
| 908 |
|
| 909 |
-
//
|
| 910 |
setUserSubmissions(prev => {
|
| 911 |
const current = prev[taskId] || [];
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
const next = (optimisticId && current.some(s => s._id === optimisticId)) ? mapped : [{ ...created, isOwner: true }, ...current];
|
| 915 |
return { ...prev, [taskId]: next };
|
| 916 |
});
|
| 917 |
|
| 918 |
// Defer state updates and minimal refetch
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
withPreservedCardOffset(taskId, () => {
|
| 925 |
-
React.startTransition(() => {
|
| 926 |
-
setTranslationText({ ...translationText, [taskId]: '' });
|
| 927 |
-
setSelectedGroups({ ...selectedGroups, [taskId]: 0 });
|
| 928 |
-
// Narrow refetch: only this task's submissions. Build off-DOM then swap in one commit
|
| 929 |
-
api.get(`/api/submissions/by-source/${taskId}`).then(r => {
|
| 930 |
-
const list = (r.data && r.data.submissions) || [];
|
| 931 |
-
flushSync(() => {
|
| 932 |
-
setUserSubmissions(prev => ({ ...prev, [taskId]: list }));
|
| 933 |
-
});
|
| 934 |
-
requestAnimationFrame(() => setSpacerHeights(prev => ({ ...prev, [taskId]: 0 })));
|
| 935 |
-
}).catch(() => {
|
| 936 |
-
requestAnimationFrame(() => setSpacerHeights(prev => ({ ...prev, [taskId]: 0 })));
|
| 937 |
-
});
|
| 938 |
-
});
|
| 939 |
-
});
|
| 940 |
-
} else {
|
| 941 |
-
// Safari: skip refetch to avoid extra reflow; just clear local fields
|
| 942 |
React.startTransition(() => {
|
| 943 |
setTranslationText({ ...translationText, [taskId]: '' });
|
| 944 |
setSelectedGroups({ ...selectedGroups, [taskId]: 0 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 945 |
});
|
| 946 |
-
}
|
| 947 |
} else {
|
| 948 |
console.error('Failed to submit translation:', response.data);
|
| 949 |
}
|
|
@@ -2331,7 +2297,7 @@ const TutorialTasks: React.FC = () => {
|
|
| 2331 |
|
| 2332 |
{/* All Submissions for this Task */}
|
| 2333 |
{userSubmissions[task._id] && userSubmissions[task._id].length > 0 && (
|
| 2334 |
-
<div ref={(el) => { submissionsContainerRefs.current[task._id] = el; }} className="bg-gradient-to-r from-white to-indigo-50 rounded-xl p-6 mb-6 border border-stone-200" style={{ overflowAnchor: 'none'
|
| 2335 |
<div className="flex items-center justify-between mb-4">
|
| 2336 |
<div className="flex items-center space-x-2">
|
| 2337 |
<div className="bg-indigo-100 rounded-full p-1">
|
|
@@ -2358,7 +2324,7 @@ const TutorialTasks: React.FC = () => {
|
|
| 2358 |
expandedSections[task._id]
|
| 2359 |
? 'max-h-none overflow-visible'
|
| 2360 |
: 'max-h-0 overflow-hidden'
|
| 2361 |
-
}`}
|
| 2362 |
{userSubmissions[task._id].map((submission, index) => (
|
| 2363 |
<div key={submission._id} className="bg-white rounded-lg p-3 border border-stone-200 flex flex-col justify-between h-full">
|
| 2364 |
<div className="flex items-center justify-between mb-2">
|
|
|
|
| 1 |
import React, { useState, useEffect, useCallback, useRef, useLayoutEffect } from 'react';
|
|
|
|
| 2 |
import { useNavigate } from 'react-router-dom';
|
| 3 |
import { api } from '../services/api';
|
| 4 |
import {
|
|
|
|
| 867 |
setSubmitting({ ...submitting, [taskId]: true });
|
| 868 |
});
|
| 869 |
const user = JSON.parse(localStorage.getItem('user') || '{}');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 870 |
const response = await new Promise((resolve) => requestAnimationFrame(async () => {
|
| 871 |
const res = await api.post('/api/submissions', {
|
| 872 |
sourceTextId: taskId,
|
| 873 |
transcreation: text,
|
| 874 |
groupNumber: group,
|
| 875 |
culturalAdaptations: [],
|
| 876 |
+
username: user.name || 'Unknown'
|
| 877 |
});
|
| 878 |
resolve(res);
|
| 879 |
})) as any;
|
|
|
|
| 882 |
const created = response.data;
|
| 883 |
console.log('Submission created successfully:', created);
|
| 884 |
|
| 885 |
+
// Optimistic append to avoid large reflow from full refetch
|
| 886 |
setUserSubmissions(prev => {
|
| 887 |
const current = prev[taskId] || [];
|
| 888 |
+
// Put newest first like server returns
|
| 889 |
+
const next = [{ ...created, isOwner: true }, ...current];
|
|
|
|
| 890 |
return { ...prev, [taskId]: next };
|
| 891 |
});
|
| 892 |
|
| 893 |
// Defer state updates and minimal refetch
|
| 894 |
+
// Measure grid height and set spacer before refetch to keep layout height constant
|
| 895 |
+
const gridEl = submissionsGridRefs.current[taskId];
|
| 896 |
+
const gridHeight = gridEl ? gridEl.getBoundingClientRect().height : 0;
|
| 897 |
+
if (gridHeight > 0) setSpacerHeights(prev => ({ ...prev, [taskId]: gridHeight }));
|
| 898 |
+
withPreservedCardOffset(taskId, () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 899 |
React.startTransition(() => {
|
| 900 |
setTranslationText({ ...translationText, [taskId]: '' });
|
| 901 |
setSelectedGroups({ ...selectedGroups, [taskId]: 0 });
|
| 902 |
+
// Narrow refetch: only this task's submissions
|
| 903 |
+
api.get(`/api/submissions/by-source/${taskId}`).then(r => {
|
| 904 |
+
const list = (r.data && r.data.submissions) || [];
|
| 905 |
+
setUserSubmissions(prev => ({ ...prev, [taskId]: list }));
|
| 906 |
+
// Release spacer one frame after list update to avoid jump
|
| 907 |
+
requestAnimationFrame(() => setSpacerHeights(prev => ({ ...prev, [taskId]: 0 })));
|
| 908 |
+
}).catch(() => {
|
| 909 |
+
requestAnimationFrame(() => setSpacerHeights(prev => ({ ...prev, [taskId]: 0 })));
|
| 910 |
+
});
|
| 911 |
});
|
| 912 |
+
});
|
| 913 |
} else {
|
| 914 |
console.error('Failed to submit translation:', response.data);
|
| 915 |
}
|
|
|
|
| 2297 |
|
| 2298 |
{/* All Submissions for this Task */}
|
| 2299 |
{userSubmissions[task._id] && userSubmissions[task._id].length > 0 && (
|
| 2300 |
+
<div ref={(el) => { submissionsContainerRefs.current[task._id] = el; }} className="bg-gradient-to-r from-white to-indigo-50 rounded-xl p-6 mb-6 border border-stone-200" style={{ overflowAnchor: 'none' }}>
|
| 2301 |
<div className="flex items-center justify-between mb-4">
|
| 2302 |
<div className="flex items-center space-x-2">
|
| 2303 |
<div className="bg-indigo-100 rounded-full p-1">
|
|
|
|
| 2324 |
expandedSections[task._id]
|
| 2325 |
? 'max-h-none overflow-visible'
|
| 2326 |
: 'max-h-0 overflow-hidden'
|
| 2327 |
+
}`}>
|
| 2328 |
{userSubmissions[task._id].map((submission, index) => (
|
| 2329 |
<div key={submission._id} className="bg-white rounded-lg p-3 border border-stone-200 flex flex-col justify-between h-full">
|
| 2330 |
<div className="flex items-center justify-between mb-2">
|