linguabot commited on
Commit
826aed7
·
verified ·
1 Parent(s): 47ff45d

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. client/src/components/Refinity.tsx +104 -26
client/src/components/Refinity.tsx CHANGED
@@ -68,10 +68,31 @@ const Refinity: React.FC = () => {
68
  return 'task';
69
  });
70
  const [tasks, setTasks] = React.useState<Task[]>([]);
71
- const [selectedTaskId, setSelectedTaskId] = React.useState<string>('');
 
 
 
 
 
 
 
72
  const [versions, setVersions] = React.useState<Version[]>([]);
73
- const [currentVersionId, setCurrentVersionId] = React.useState<string | null>(null);
74
- const [previewVersionId, setPreviewVersionId] = React.useState<string | null>(null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  const [isFullscreen, setIsFullscreen] = React.useState<boolean>(() => {
76
  try {
77
  const h = String(window.location.hash || '');
@@ -177,6 +198,10 @@ const Refinity: React.FC = () => {
177
  const task = React.useMemo(() => tasks.find(t => t.id === selectedTaskId) || tasks[0], [tasks, selectedTaskId]);
178
  const taskVersions = React.useMemo(() => versions.filter(v => v.taskId === (task?.id || '')), [versions, task?.id]);
179
  // Load tasks
 
 
 
 
180
  React.useEffect(() => {
181
  (async () => {
182
  try {
@@ -188,11 +213,27 @@ const Refinity: React.FC = () => {
188
  initialRouteRef.current = { stage: desiredStage, taskId: params.taskId, versionId: params.versionId, previewId: params.previewId, fullscreen };
189
  if (fullscreen) setIsFullscreen(true);
190
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
192
  const resp = await fetch(`${base}/api/refinity/tasks`);
193
  const data: any[] = await resp.json().catch(()=>[]);
194
  const normalized: Task[] = Array.isArray(data) ? data.map(d => ({ id: d._id, title: d.title, sourceText: d.sourceText, createdBy: d.createdBy })) : [];
195
  setTasks(normalized);
 
196
  // Apply initial route task selection if available
197
  const initTaskId = initialRouteRef.current?.taskId;
198
  if (normalized.length) {
@@ -211,11 +252,23 @@ const Refinity: React.FC = () => {
211
  (async () => {
212
  if (!task?.id) { setVersions([]); return; }
213
  try {
 
 
 
 
 
 
 
 
 
 
 
214
  const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
215
  const resp = await fetch(`${base}/api/refinity/tasks/${encodeURIComponent(task.id)}/versions`);
216
  const data: any[] = await resp.json().catch(()=>[]);
217
  const normalized: Version[] = Array.isArray(data) ? data.map(d => ({ id: d._id, taskId: d.taskId, originalAuthor: d.originalAuthor, revisedBy: d.revisedBy, versionNumber: d.versionNumber, content: d.content, parentVersionId: d.parentVersionId })) : [];
218
  setVersions(normalized);
 
219
  // Restore stage/version from initial route if present
220
  const init = initialRouteRef.current;
221
  if (init?.stage && normalized.length) {
@@ -227,18 +280,23 @@ const Refinity: React.FC = () => {
227
  } else if (init.stage === 'preview' && init.previewId && normalized.some(v => v.id === init.previewId)) {
228
  setPreviewVersionId(init.previewId);
229
  setStage('preview');
 
 
 
 
 
 
 
230
  } else if (init.stage === 'flow') {
231
  setStage('flow');
232
- } else {
233
- setCurrentVersionId(normalized[normalized.length-1].id);
234
- setStage('flow');
235
  }
236
  } finally {
237
  // small defer to avoid immediate hash writes during restore
238
  setTimeout(() => { restoringRef.current = false; }, 50);
239
  }
240
  } else {
241
- setCurrentVersionId(normalized.length ? normalized[normalized.length-1].id : null);
 
242
  }
243
  } catch { setVersions([]); }
244
  })();
@@ -318,6 +376,8 @@ const Refinity: React.FC = () => {
318
  return Math.min(Math.max(taskVersions.length - 1, 0), flowIndex);
319
  }, [taskVersions, currentVersionId, flowIndex]);
320
  const centerVersionId = React.useMemo(() => (taskVersions[flowIndex]?.id) || '', [taskVersions, flowIndex]);
 
 
321
 
322
  // Keyboard navigation disabled per request (use arrow buttons only)
323
 
@@ -1290,29 +1350,43 @@ const Refinity: React.FC = () => {
1290
 
1291
  {/* Stage: Preview full text */}
1292
  {stage === 'preview' && (
1293
- <PreviewPane
1294
- version={taskVersions.find(v => v.id === previewVersionId) || (flowIndex >= 0 ? (taskVersions[flowIndex] || null) : null)}
1295
- onBack={()=>setStage('flow')}
1296
- onEdit={()=>{ if (previewVersionId) { setCurrentVersionId(previewVersionId); setStage('editor'); } }}
1297
- compareInitially={showPreviewDiff}
1298
- />
 
 
 
 
 
 
 
1299
  )}
1300
 
1301
  {/* Stage 3: Editor */}
1302
  {stage === 'editor' && (
1303
- <EditorPane
1304
- source={task?.sourceText || ''}
1305
- initialTranslation={taskVersions.find(v => v.id === currentVersionId)?.content || ''}
1306
- onBack={()=>setStage('flow')}
1307
- onSave={handleSaveRevision}
1308
- onSaveEdit={editingVersionId ? ((text)=>saveEditedVersion(editingVersionId, text)) : undefined}
1309
- taskTitle={task?.title || 'Task'}
1310
- username={username}
1311
- nextVersionNumber={((versions.filter(v=>v.taskId===(task?.id||'')).slice(-1)[0]?.versionNumber) || 0) + 1}
1312
- isFullscreen={isFullscreen}
1313
- onToggleFullscreen={()=>setIsFullscreen(v=>!v)}
1314
- versionId={currentVersionId || ''}
1315
- />
 
 
 
 
 
 
 
1316
  )}
1317
  </div>
1318
  </div>
@@ -1322,6 +1396,10 @@ const Refinity: React.FC = () => {
1322
  const EditorPane: React.FC<{ source: string; initialTranslation: string; onBack: ()=>void; onSave: (text: string)=>void; onSaveEdit?: (text: string)=>void; taskTitle: string; username: string; nextVersionNumber: number; isFullscreen: boolean; onToggleFullscreen: ()=>void; versionId: string }>=({ source, initialTranslation, onBack, onSave, onSaveEdit, taskTitle, username, nextVersionNumber, isFullscreen, onToggleFullscreen, versionId })=>{
1323
  const [text, setText] = React.useState<string>(initialTranslation);
1324
  const [saving, setSaving] = React.useState(false);
 
 
 
 
1325
  const [diffHtml, setDiffHtml] = React.useState<string>('');
1326
  const [showDiff, setShowDiff] = React.useState<boolean>(false);
1327
  const [revDownloadOpen, setRevDownloadOpen] = React.useState<boolean>(false);
 
68
  return 'task';
69
  });
70
  const [tasks, setTasks] = React.useState<Task[]>([]);
71
+ const [selectedTaskId, setSelectedTaskId] = React.useState<string>(() => {
72
+ try {
73
+ const h = String(window.location.hash || '');
74
+ const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
75
+ const params = new URLSearchParams(q);
76
+ return String(params.get('taskId') || '');
77
+ } catch { return ''; }
78
+ });
79
  const [versions, setVersions] = React.useState<Version[]>([]);
80
+ const [currentVersionId, setCurrentVersionId] = React.useState<string | null>(() => {
81
+ try {
82
+ const h = String(window.location.hash || '');
83
+ const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
84
+ const params = new URLSearchParams(q);
85
+ return String(params.get('versionId') || '') || null;
86
+ } catch { return null; }
87
+ });
88
+ const [previewVersionId, setPreviewVersionId] = React.useState<string | null>(() => {
89
+ try {
90
+ const h = String(window.location.hash || '');
91
+ const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
92
+ const params = new URLSearchParams(q);
93
+ return String(params.get('previewId') || '') || null;
94
+ } catch { return null; }
95
+ });
96
  const [isFullscreen, setIsFullscreen] = React.useState<boolean>(() => {
97
  try {
98
  const h = String(window.location.hash || '');
 
198
  const task = React.useMemo(() => tasks.find(t => t.id === selectedTaskId) || tasks[0], [tasks, selectedTaskId]);
199
  const taskVersions = React.useMemo(() => versions.filter(v => v.taskId === (task?.id || '')), [versions, task?.id]);
200
  // Load tasks
201
+ // Simple session cache keys
202
+ const TASKS_CACHE_KEY = 'refinity_tasks_cache_v1';
203
+ const versionsCacheKey = React.useCallback((taskId: string) => `refinity_versions_cache_${taskId}_v1`, []);
204
+
205
  React.useEffect(() => {
206
  (async () => {
207
  try {
 
213
  initialRouteRef.current = { stage: desiredStage, taskId: params.taskId, versionId: params.versionId, previewId: params.previewId, fullscreen };
214
  if (fullscreen) setIsFullscreen(true);
215
  }
216
+ // Hydrate from cache immediately
217
+ try {
218
+ const cached = JSON.parse(sessionStorage.getItem(TASKS_CACHE_KEY) || '[]');
219
+ if (Array.isArray(cached) && cached.length && tasks.length === 0) {
220
+ setTasks(cached);
221
+ if (!selectedTaskId) {
222
+ const initTaskId = initialRouteRef.current?.taskId;
223
+ if (initTaskId && cached.some((t:any)=>t.id===initTaskId)) {
224
+ setSelectedTaskId(initTaskId);
225
+ } else {
226
+ setSelectedTaskId(cached[0].id);
227
+ }
228
+ }
229
+ }
230
+ } catch {}
231
  const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
232
  const resp = await fetch(`${base}/api/refinity/tasks`);
233
  const data: any[] = await resp.json().catch(()=>[]);
234
  const normalized: Task[] = Array.isArray(data) ? data.map(d => ({ id: d._id, title: d.title, sourceText: d.sourceText, createdBy: d.createdBy })) : [];
235
  setTasks(normalized);
236
+ try { sessionStorage.setItem(TASKS_CACHE_KEY, JSON.stringify(normalized)); } catch {}
237
  // Apply initial route task selection if available
238
  const initTaskId = initialRouteRef.current?.taskId;
239
  if (normalized.length) {
 
252
  (async () => {
253
  if (!task?.id) { setVersions([]); return; }
254
  try {
255
+ // Hydrate versions from cache for instant UI
256
+ try {
257
+ const cached = JSON.parse(sessionStorage.getItem(versionsCacheKey(task.id)) || '[]');
258
+ if (Array.isArray(cached) && cached.length) {
259
+ setVersions(cached);
260
+ // If versionId missing but stage requests editor, default to latest cached
261
+ if (stage === 'editor' && !currentVersionId) {
262
+ setCurrentVersionId(cached[cached.length - 1]?.id || null);
263
+ }
264
+ }
265
+ } catch {}
266
  const base = ((api.defaults as any)?.baseURL as string || '').replace(/\/$/, '');
267
  const resp = await fetch(`${base}/api/refinity/tasks/${encodeURIComponent(task.id)}/versions`);
268
  const data: any[] = await resp.json().catch(()=>[]);
269
  const normalized: Version[] = Array.isArray(data) ? data.map(d => ({ id: d._id, taskId: d.taskId, originalAuthor: d.originalAuthor, revisedBy: d.revisedBy, versionNumber: d.versionNumber, content: d.content, parentVersionId: d.parentVersionId })) : [];
270
  setVersions(normalized);
271
+ try { sessionStorage.setItem(versionsCacheKey(task.id), JSON.stringify(normalized)); } catch {}
272
  // Restore stage/version from initial route if present
273
  const init = initialRouteRef.current;
274
  if (init?.stage && normalized.length) {
 
280
  } else if (init.stage === 'preview' && init.previewId && normalized.some(v => v.id === init.previewId)) {
281
  setPreviewVersionId(init.previewId);
282
  setStage('preview');
283
+ } else if (init.stage === 'editor') {
284
+ // Stay in editor; choose latest if specific version missing
285
+ setCurrentVersionId(normalized[normalized.length-1]?.id || null);
286
+ setStage('editor');
287
+ } else if (init.stage === 'preview') {
288
+ setPreviewVersionId(normalized[normalized.length-1]?.id || null);
289
+ setStage('preview');
290
  } else if (init.stage === 'flow') {
291
  setStage('flow');
 
 
 
292
  }
293
  } finally {
294
  // small defer to avoid immediate hash writes during restore
295
  setTimeout(() => { restoringRef.current = false; }, 50);
296
  }
297
  } else {
298
+ // No init; do not force stage change if already 'editor' or 'preview'
299
+ if (!currentVersionId) setCurrentVersionId(normalized.length ? normalized[normalized.length-1].id : null);
300
  }
301
  } catch { setVersions([]); }
302
  })();
 
376
  return Math.min(Math.max(taskVersions.length - 1, 0), flowIndex);
377
  }, [taskVersions, currentVersionId, flowIndex]);
378
  const centerVersionId = React.useMemo(() => (taskVersions[flowIndex]?.id) || '', [taskVersions, flowIndex]);
379
+ const currentVersion = React.useMemo(() => taskVersions.find(v => v.id === currentVersionId) || null, [taskVersions, currentVersionId]);
380
+ const previewVersion = React.useMemo(() => taskVersions.find(v => v.id === previewVersionId) || null, [taskVersions, previewVersionId]);
381
 
382
  // Keyboard navigation disabled per request (use arrow buttons only)
383
 
 
1350
 
1351
  {/* Stage: Preview full text */}
1352
  {stage === 'preview' && (
1353
+ <>
1354
+ {!previewVersion && (
1355
+ <div className="rounded-xl ring-1 ring-gray-200 shadow-sm overflow-hidden bg-white/60 min-h-[280px] animate-pulse" />
1356
+ )}
1357
+ {previewVersion && (
1358
+ <PreviewPane
1359
+ version={previewVersion || (flowIndex >= 0 ? (taskVersions[flowIndex] || null) : null)}
1360
+ onBack={()=>setStage('flow')}
1361
+ onEdit={()=>{ if (previewVersionId) { setCurrentVersionId(previewVersionId); setStage('editor'); } }}
1362
+ compareInitially={showPreviewDiff}
1363
+ />
1364
+ )}
1365
+ </>
1366
  )}
1367
 
1368
  {/* Stage 3: Editor */}
1369
  {stage === 'editor' && (
1370
+ <>
1371
+ {(!currentVersion || !task?.sourceText) && (
1372
+ <div className="rounded-xl ring-1 ring-gray-200 shadow-sm overflow-hidden bg-white/60 min-h-[520px] animate-pulse" />
1373
+ )}
1374
+ {currentVersion && task?.sourceText && (
1375
+ <EditorPane
1376
+ source={task?.sourceText || ''}
1377
+ initialTranslation={currentVersion?.content || ''}
1378
+ onBack={()=>setStage('flow')}
1379
+ onSave={handleSaveRevision}
1380
+ onSaveEdit={editingVersionId ? ((text)=>saveEditedVersion(editingVersionId, text)) : undefined}
1381
+ taskTitle={task?.title || 'Task'}
1382
+ username={username}
1383
+ nextVersionNumber={((versions.filter(v=>v.taskId===(task?.id||'')).slice(-1)[0]?.versionNumber) || 0) + 1}
1384
+ isFullscreen={isFullscreen}
1385
+ onToggleFullscreen={()=>setIsFullscreen(v=>!v)}
1386
+ versionId={currentVersionId || ''}
1387
+ />
1388
+ )}
1389
+ </>
1390
  )}
1391
  </div>
1392
  </div>
 
1396
  const EditorPane: React.FC<{ source: string; initialTranslation: string; onBack: ()=>void; onSave: (text: string)=>void; onSaveEdit?: (text: string)=>void; taskTitle: string; username: string; nextVersionNumber: number; isFullscreen: boolean; onToggleFullscreen: ()=>void; versionId: string }>=({ source, initialTranslation, onBack, onSave, onSaveEdit, taskTitle, username, nextVersionNumber, isFullscreen, onToggleFullscreen, versionId })=>{
1397
  const [text, setText] = React.useState<string>(initialTranslation);
1398
  const [saving, setSaving] = React.useState(false);
1399
+ // Sync text with incoming props when version/context changes (e.g., refresh -> data loads)
1400
+ React.useEffect(() => {
1401
+ setText(initialTranslation || '');
1402
+ }, [versionId, initialTranslation]);
1403
  const [diffHtml, setDiffHtml] = React.useState<string>('');
1404
  const [showDiff, setShowDiff] = React.useState<boolean>(false);
1405
  const [revDownloadOpen, setRevDownloadOpen] = React.useState<boolean>(false);