import React, { useState, useContext, useEffect, useCallback, useRef, } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { Alert, AlertColor, Button, Chip, Container, Paper, Pagination, Skeleton, Stack, Tabs, Tab, TextField, Typography, } from '@mui/material'; import GraphG6, { GraphProps } from './graph-g6'; import LoadingContext from './loading-context'; import SnackbarContext from './snackbar-context'; export type Parsing = 'Naive' | 'Hybrid'; export type StrategySnakeCase = 'parse_only' | 'ground' | 'complete' | 'stable' | 'stable_counting_a' | 'stable_counting_b' | 'stable_nogood'; export type StrategyCamelCase = 'ParseOnly' | 'Ground' | 'Complete' | 'Stable' | 'StableCountingA' | 'StableCountingB' | 'StableNogood'; export const STRATEGIES_WITHOUT_PARSE: StrategyCamelCase[] = ['Ground', 'Complete', 'Stable', 'StableCountingA', 'StableCountingB', 'StableNogood']; export interface AcAndGraph { ac: string[], graph: GraphProps, } export type AcsWithGraphsOpt = { type: 'None', } | { type: 'Error', content: string } | { type: 'Some', content: AcAndGraph[] }; export type Task = { type: 'Parse', } | { type: 'Solve', content: StrategyCamelCase, }; export interface AdfProblemInfo { name: string, code: string, parsing_used: Parsing, // NOTE: the keys are really only strategies acs_per_strategy: { [key in StrategySnakeCase]: AcsWithGraphsOpt }, running_tasks: Task[], } export function acsWithGraphOptToColor(status: AcsWithGraphsOpt, running: boolean): AlertColor { if (running) { return 'warning'; } switch (status.type) { case 'None': return 'info'; case 'Error': return 'error'; case 'Some': return 'success'; default: throw new Error('Unknown type union variant (cannot occur)'); } } export function acsWithGraphOptToText(status: AcsWithGraphsOpt, running: boolean): string { if (running) { return 'Running'; } switch (status.type) { case 'None': return 'Not attempted'; case 'Error': return 'Failed'; case 'Some': return 'Done'; default: throw new Error('Unknown type union variant (cannot occur)'); } } function AdfDetails() { const { adfName } = useParams(); const navigate = useNavigate(); const { setLoading } = useContext(LoadingContext); const { status: snackbarInfo, setStatus: setSnackbarInfo } = useContext(SnackbarContext); const [problem, setProblem] = useState(); const [tab, setTab] = useState('parse_only'); const [solutionIndex, setSolutionIndex] = useState(0); const isFirstRender = useRef(true); const fetchProblem = useCallback( () => { fetch(`${process.env.NODE_ENV === 'development' ? '//localhost:8080' : ''}/adf/${adfName}`, { method: 'GET', credentials: process.env.NODE_ENV === 'development' ? 'include' : 'same-origin', headers: { 'Content-Type': 'application/json', }, }) .then((res) => { switch (res.status) { case 200: res.json().then((resProblem) => { setProblem(resProblem); }); break; default: navigate('/'); break; } }); }, [setProblem], ); const solveHandler = useCallback( (strategy: StrategyCamelCase) => { setLoading(true); fetch(`${process.env.NODE_ENV === 'development' ? '//localhost:8080' : ''}/adf/${adfName}/solve`, { method: 'PUT', credentials: process.env.NODE_ENV === 'development' ? 'include' : 'same-origin', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ strategy }), }) .then((res) => { switch (res.status) { case 200: setSnackbarInfo({ message: 'Solving problem now...', severity: 'success', potentialUserChange: false }); fetchProblem(); break; default: setSnackbarInfo({ message: 'Something went wrong tying to solve the problem.', severity: 'error', potentialUserChange: false }); break; } }) .finally(() => setLoading(false)); }, [adfName], ); const deleteHandler = useCallback( () => { setLoading(true); fetch(`${process.env.NODE_ENV === 'development' ? '//localhost:8080' : ''}/adf/${adfName}`, { method: 'DELETE', credentials: process.env.NODE_ENV === 'development' ? 'include' : 'same-origin', headers: { 'Content-Type': 'application/json', }, }) .then((res) => { switch (res.status) { case 200: setSnackbarInfo({ message: 'ADF Problem deleted.', severity: 'success', potentialUserChange: false }); navigate('/'); break; default: break; } }) .finally(() => setLoading(false)); }, [adfName], ); useEffect( () => { // TODO: having the info if the user may have changed on the snackbar info // is a bit lazy and unclean; be better! if (isFirstRender.current || snackbarInfo?.potentialUserChange) { isFirstRender.current = false; fetchProblem(); } }, [snackbarInfo?.potentialUserChange], ); useEffect( () => { // if there is a running task, fetch problems again after 20 seconds let timeout: ReturnType; if (problem && problem.running_tasks.length > 0) { timeout = setTimeout(() => fetchProblem(), 20000); } return () => { if (timeout) { clearTimeout(timeout); } }; }, [problem], ); const acsOpt = problem?.acs_per_strategy[tab]; const acsContent = acsOpt?.type === 'Some' ? acsOpt.content : undefined; const tabCamelCase: StrategyCamelCase = tab.replace(/^([a-z])/, (_, p1) => p1.toUpperCase()).replace(/_([a-z])/g, (_, p1) => `${p1.toUpperCase()}`) as StrategyCamelCase; return ( <> ADF-BDD.DEV {problem ? ( <> {problem.name} { navigator.clipboard.writeText(problem.code); setSnackbarInfo({ message: 'Code copied to clipboard!', severity: 'info', potentialUserChange: false }); }} /> { setTab(newTab); setSolutionIndex(0); }} variant="scrollable" scrollButtons="auto" > t.type === 'Parse'))} label={`${problem.parsing_used} Parsing`} sx={{ cursor: 'inherit' }} />} /> {STRATEGIES_WITHOUT_PARSE.map((strategy) => { const spaced = strategy.replace(/([A-Za-z])([A-Z])/g, '$1 $2'); const snakeCase = strategy.replace(/^([A-Z])/, (_, p1) => p1.toLowerCase()).replace(/([A-Z])/g, (_, p1) => `_${p1.toLowerCase()}`) as StrategySnakeCase; const status = problem.acs_per_strategy[snakeCase]; const running = problem.running_tasks.some((t: Task) => t.type === 'Solve' && t.content === strategy); const color = acsWithGraphOptToColor(status, running); return } />; })} {acsContent && acsContent.length > 1 && ( <> Models:
setSolutionIndex(newIdx - 1)} /> )} {problem.running_tasks.some((t: Task) => (tab === 'parse_only' && t.type === 'Parse') || (t.type === 'Solve' && t.content === tabCamelCase)) ? ( Working hard to solve the problem right now... ) : ( <> {acsContent && acsContent.length > 0 && ( )} {acsContent && acsContent.length === 0 && ( The problem has no models for this strategy. )} {!acsContent && acsOpt?.type === 'Error' && ( An error occurred: {acsOpt.content} )} {!acsContent && acsOpt?.type === 'None' && ( <> This strategy was not attempted yet. )} )} ) : ( <> )}
); } export default AdfDetails;