diff --git a/app/autocorrection.ts b/app/autocorrection.ts index b12fd0d..3e0e173 100644 --- a/app/autocorrection.ts +++ b/app/autocorrection.ts @@ -64,7 +64,7 @@ export default class AutoCorrection { ); }); - ipcMain.on(AUTOCORRECTION_CANCEL, (event: IpcMainEvent, arg) => { + ipcMain.on(AUTOCORRECTION_CANCEL, (event: IpcMainEvent) => { const { sender } = event; this.cancelRequest = true; sender.send(AUTOCORRECTION_CANCEL_PENDING); diff --git a/app/components/ConditionalCommentSettings.tsx b/app/components/ConditionalCommentSettings.tsx index 26ccf24..0dbef38 100644 --- a/app/components/ConditionalCommentSettings.tsx +++ b/app/components/ConditionalCommentSettings.tsx @@ -2,16 +2,14 @@ /* eslint-disable @typescript-eslint/no-empty-interface */ /* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import { Theme, withStyles } from '@material-ui/core/styles'; import AddCircleIcon from '@material-ui/icons/AddCircle'; import Typography from '@material-ui/core/Typography'; import { - Fade, Grid, IconButton, Paper, Slider, - Tooltip, + Theme, useTheme, } from '@material-ui/core'; import { useDispatch, useSelector } from 'react-redux'; @@ -22,7 +20,12 @@ import { settingsUpdateConditionalCommentValue, } from '../model/SettingsSlice'; -function ValueLabelComponent(props: any) { +function ValueLabelComponent(props: { + children; + value: number; + comments: string[]; + theme: Theme; +}) { const { children, value, comments, theme } = props; return ( <> diff --git a/app/components/ConditionalCommentTextInput.tsx b/app/components/ConditionalCommentTextInput.tsx index 2d68787..34c8f5c 100644 --- a/app/components/ConditionalCommentTextInput.tsx +++ b/app/components/ConditionalCommentTextInput.tsx @@ -2,7 +2,6 @@ /* eslint-disable react/prop-types */ import { IconButton, TextField } from '@material-ui/core'; import CancelIcon from '@material-ui/icons/Cancel'; -import { relative } from 'path'; import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import ConditionalComment from '../model/ConditionalComment'; diff --git a/app/components/LoadingItem.tsx b/app/components/LoadingItem.tsx index 4035c14..9464bca 100644 --- a/app/components/LoadingItem.tsx +++ b/app/components/LoadingItem.tsx @@ -2,7 +2,12 @@ import { Grid, CircularProgress, Typography } from '@material-ui/core'; import React from 'react'; import DoneIcon from '@material-ui/icons/Done'; -export default function LoadingItem(props: any): JSX.Element { +export type LoadingItemProps = { + complete: boolean; + message: string; +}; + +export default function LoadingItem(props: LoadingItemProps): JSX.Element { const { complete, message } = props; const loadingIcon = complete ? ( diff --git a/app/components/LoadingItemList.tsx b/app/components/LoadingItemList.tsx index 4d2d4ac..30b2943 100644 --- a/app/components/LoadingItemList.tsx +++ b/app/components/LoadingItemList.tsx @@ -1,16 +1,20 @@ import { List, ListItem } from '@material-ui/core'; import React from 'react'; -import LoadingItem from './LoadingItem'; +import LoadingItem, { LoadingItemProps } from './LoadingItem'; -export default function LoadingItemList(props: any): JSX.Element { +type LoadingItemListProps = { + progress: (LoadingItemProps & { active: boolean })[]; +}; + +export default function LoadingItemList( + props: LoadingItemListProps +): JSX.Element { const { progress } = props; - const activeItems = progress.filter( - (item: { active: boolean }) => item.active - ); + const activeItems = progress.filter((item) => item.active); return ( - {activeItems.map((item: { message: string; complete: boolean }) => { + {activeItems.map((item) => { return ( diff --git a/app/components/OutputFormatSelect.tsx b/app/components/OutputFormatSelect.tsx index c4cd96d..4e7c572 100644 --- a/app/components/OutputFormatSelect.tsx +++ b/app/components/OutputFormatSelect.tsx @@ -6,7 +6,7 @@ import { selectSettingsExport, settingsSetExport, } from '../model/SettingsSlice'; -import { ParserType } from '../parser/Parser'; +import ParserType from '../parser/ParserType'; const OutputFormatSelect = () => { const dispatch = useDispatch(); diff --git a/app/containers/CorrectionViewPage.tsx b/app/containers/CorrectionViewPage.tsx index bb68275..6f6c729 100644 --- a/app/containers/CorrectionViewPage.tsx +++ b/app/containers/CorrectionViewPage.tsx @@ -27,7 +27,6 @@ import { } from '../model/Selectors'; import Sheet from '../model/Sheet'; -import { sheetsUpsertOne } from '../model/SheetSlice'; import { serializeTerm } from '../utils/Formatter'; import './SplitPane.css'; @@ -64,6 +63,7 @@ export default function CorrectionViewPage() { ); } }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [index]); function handleCloseDialog() { diff --git a/app/containers/OverviewPage.tsx b/app/containers/OverviewPage.tsx index 4506f24..bb907a5 100644 --- a/app/containers/OverviewPage.tsx +++ b/app/containers/OverviewPage.tsx @@ -2,6 +2,6 @@ import React from 'react'; import Overview from '../features/overview/Overview'; -export default function OverviewPage(props: any) { +export default function OverviewPage(props) { return ; } diff --git a/app/containers/Root.tsx b/app/containers/Root.tsx index 86b17d2..226bb13 100644 --- a/app/containers/Root.tsx +++ b/app/containers/Root.tsx @@ -6,6 +6,7 @@ import { AnyAction, EnhancedStore } from '@reduxjs/toolkit'; import Providers from './Providers'; type Props = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any store: EnhancedStore; history: History; }; diff --git a/app/containers/TitleBar.tsx b/app/containers/TitleBar.tsx index fbefc3f..d8ade3d 100644 --- a/app/containers/TitleBar.tsx +++ b/app/containers/TitleBar.tsx @@ -8,6 +8,7 @@ import { useSelector } from 'react-redux'; import { useTheme, Snackbar } from '@material-ui/core'; import { remote } from 'electron'; import { Alert } from '@material-ui/lab'; +import { MenuItem } from 'frameless-titlebar/dist/title-bar/typings'; import { selectRecentPaths, selectWorkspacePath, @@ -109,7 +110,7 @@ export default function TitleBar(props: TitleBarProps) { recentPaths, setOpenFileError, setReload - ) as any + ) as MenuItem[] | undefined } theme={{ bar: { diff --git a/app/exporter.ts b/app/exporter.ts index 77177fe..78dbf9d 100644 --- a/app/exporter.ts +++ b/app/exporter.ts @@ -11,10 +11,11 @@ import { } from './constants/ExportIPC'; import ConditionalComment from './model/ConditionalComment'; import Correction from './model/Correction'; -import Parser, { ParserType } from './parser/Parser'; +import Parser from './parser/Parser'; import instanciateParser from './parser/ParserUtil'; import { serializeCorrection } from './utils/Formatter'; import { loadFilesFromWorkspaceMainProcess } from './utils/FileAccess'; +import ParserType from './parser/ParserType'; export interface ExportProgress { steps: { diff --git a/app/features/correction/CorrectionOverview.tsx b/app/features/correction/CorrectionOverview.tsx index 7fb9263..ba4ff5f 100644 --- a/app/features/correction/CorrectionOverview.tsx +++ b/app/features/correction/CorrectionOverview.tsx @@ -49,6 +49,7 @@ export default function CorrectionOverview(props: CorrectionOverviewProps) { correction.status === Status.Marked ) ); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [index]); const handleClick = () => { diff --git a/app/features/media-viewer/PDFViewer.tsx b/app/features/media-viewer/PDFViewer.tsx index 54f3e24..219ea02 100644 --- a/app/features/media-viewer/PDFViewer.tsx +++ b/app/features/media-viewer/PDFViewer.tsx @@ -28,8 +28,10 @@ export default function PDFViewer(props: ViewerProps) { const textLayers = document.querySelectorAll( '.react-pdf__Page__textContent' ); - textLayers.forEach((layer: any) => { - const { style } = layer; + textLayers.forEach((layer) => { + const { style } = (layer as unknown) as { + style: { top: string; left: string; transform: string }; + }; style.top = '0'; style.left = '0'; style.transform = ''; diff --git a/app/features/media-viewer/ViewerToolbar.tsx b/app/features/media-viewer/ViewerToolbar.tsx index 8d0ed97..72861fb 100644 --- a/app/features/media-viewer/ViewerToolbar.tsx +++ b/app/features/media-viewer/ViewerToolbar.tsx @@ -113,6 +113,7 @@ export default function ViewerToolbar(props: ViewerToolbarProps) { return () => { window.removeEventListener('wheel', handleScrollEvent); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/app/importer.ts b/app/importer.ts index dbc4bd3..b3d8416 100644 --- a/app/importer.ts +++ b/app/importer.ts @@ -10,7 +10,7 @@ import { } from 'electron'; import { normalize } from 'normalizr'; import Correction from './model/Correction'; -import Parser, { ParserType } from './parser/Parser'; +import Parser from './parser/Parser'; import instanciateParser from './parser/ParserUtil'; import { IMPORT_CONFLICTS, @@ -27,6 +27,7 @@ import { getAllFilesInDirectory, } from './utils/FileAccess'; import { CorrectionSchema } from './model/NormalizationSchema'; +import ParserType from './parser/ParserType'; export interface ImportProgress { name: string; @@ -82,6 +83,7 @@ export default class Importer { // Create new workspace file if no workspace is allocated yet if (workspacePath.length === 0) { const p: string | undefined = dialog.showSaveDialogSync(this.mainWindow, { + defaultPath: `${Path.parse(path).name}.cor`, filters: [{ name: 'Correctinator', extensions: ['cor'] }], }); if (!p) { @@ -162,7 +164,8 @@ export default class Importer { const content = fs.readFileSync(importPath, Importer.ENCODING); const correction: Correction = parser.deserialize( content, - Path.basename(Path.dirname(importPath)) + Path.basename(Path.dirname(importPath)), + Path.basename(importPath) ); // Were the submissions already imported? @@ -224,7 +227,8 @@ export default class Importer { const content = zipEntry.getData().toString(Importer.ENCODING); const correction: Correction = parser.deserialize( content, - Path.basename(Path.dirname(zipEntry.entryName)) + Path.basename(Path.dirname(zipEntry.entryName)), + Path.basename(zipEntry.entryName) ); // Were the submissions already imported? @@ -381,7 +385,8 @@ export default class Importer { const parser: Parser = instanciateParser(c.parser); const correction: Correction = parser.deserialize( content, - Path.basename(Path.dirname(c.path)) + Path.basename(Path.dirname(c.path)), + Path.basename(c.path) ); corrections.push( this.ingestCorrectionFromZip( @@ -407,7 +412,8 @@ export default class Importer { const parser: Parser = instanciateParser(c.parser); const correction: Correction = parser.deserialize( content, - Path.basename(Path.dirname(c.path)) + Path.basename(Path.dirname(c.path)), + Path.basename(c.path) ); corrections.push( this.ingestCorrectionFromFolder( diff --git a/app/menu.ts b/app/menu.ts index fda31a5..9716e53 100644 --- a/app/menu.ts +++ b/app/menu.ts @@ -26,11 +26,6 @@ export default class MenuBuilder { this.setupDevelopmentEnvironment(); } - const template = - process.platform === 'darwin' - ? this.buildDarwinTemplate() - : this.buildDefaultTemplate(); - const menu = Menu.buildFromTemplate([]); Menu.setApplicationMenu(menu); diff --git a/app/menu/FileMenu.ts b/app/menu/FileMenu.ts index 1dae56a..8a4877e 100644 --- a/app/menu/FileMenu.ts +++ b/app/menu/FileMenu.ts @@ -192,6 +192,7 @@ const buildFileMenu = ( }, { label: 'Close file', + disabled: !workspace, click: async () => { unsavedChangesDialog(''); }, diff --git a/app/modals/AutoCorrectionModal.tsx b/app/modals/AutoCorrectionModal.tsx index 0a54d3c..819dfb0 100644 --- a/app/modals/AutoCorrectionModal.tsx +++ b/app/modals/AutoCorrectionModal.tsx @@ -138,7 +138,7 @@ const AutoCorrectionModal: FC = ({ ...props }) => { ) => { setAutoCorrectionProgress(progress); }; - const handleAutoCorrectionCancelPending = (_event: IpcRendererEvent) => { + const handleAutoCorrectionCancelPending = () => { setAutoCorrectionState(AutoCorrectionState.AUTOCORRECTION_CANCEL_PENDING); }; @@ -236,9 +236,10 @@ const AutoCorrectionModal: FC = ({ ...props }) => { handleProgress ); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - let content; + let content: JSX.Element | null; switch (autoCorrectionState) { case AutoCorrectionState.AUTOCORRECTION_STARTED: diff --git a/app/modals/ExportModal.tsx b/app/modals/ExportModal.tsx index 58b3297..61c3394 100644 --- a/app/modals/ExportModal.tsx +++ b/app/modals/ExportModal.tsx @@ -31,7 +31,6 @@ import DialogTitleWithCloseIcon from './DialogTitleWithCloseIcon'; import { ModalProps } from './ModalProvider'; import { selectCorrectionsBySheetId } from '../model/Selectors'; import * as ExportIPC from '../constants/ExportIPC'; -import { ParserType } from '../parser/Parser'; import { ExportProgress } from '../exporter'; import CircularProgressWithLabel from '../components/CircularProgressWithLabel'; import Status from '../model/Status'; @@ -41,8 +40,7 @@ import { } from '../model/SettingsSlice'; import OutputFormatSelect from '../components/OutputFormatSelect'; import ConditionalCommentSettings from '../components/ConditionalCommentSettings'; -import Correction from '../model/Correction'; -import { selectSheetById } from '../model/SheetSlice'; +import ParserType from '../parser/ParserType'; type ExportModalProps = ModalProps & { sheetId: string; diff --git a/app/modals/ImportModal.tsx b/app/modals/ImportModal.tsx index 8b90090..fcd1b48 100644 --- a/app/modals/ImportModal.tsx +++ b/app/modals/ImportModal.tsx @@ -26,12 +26,12 @@ import { import { CorrectionsSchema } from '../model/NormalizationSchema'; import { loadCorrections } from '../model/CorrectionsSlice'; import CircularProgressWithLabel from '../components/CircularProgressWithLabel'; -import { ParserType } from '../parser/Parser'; // eslint-disable-next-line import/no-cycle import OverwriteDuplicateSubmissionsDialog from '../dialogs/OverwriteDuplicateSubmissionsDialog'; import ConfirmationDialog from '../dialogs/ConfirmationDialog'; import { save } from '../utils/FileAccess'; import { selectSettingsGeneral } from '../model/SettingsSlice'; +import ParserType from '../parser/ParserType'; type ImportModalProps = ModalProps & { path?: string; @@ -127,6 +127,7 @@ const ImportModal: FC = ({ ...props }) => { ipcRenderer.removeListener(ImportIPC.IMPORT_FAILED, handleImportFailed); ipcRenderer.removeListener(ImportIPC.IMPORT_PROGRESS, handleProgress); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); let content; diff --git a/app/modals/ModalProvider.tsx b/app/modals/ModalProvider.tsx index 4e6395d..ea8dd37 100644 --- a/app/modals/ModalProvider.tsx +++ b/app/modals/ModalProvider.tsx @@ -16,8 +16,9 @@ const ModalContext = createContext< export default function ModalProvider(props: ModalProviderProps) { const { children } = props; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const [Modal, setModal] = useState(null); - const [modalOptions, setModalOptions] = useState(null); + const [modalOptions, setModalOptions] = useState(null); const openModal = ( component: FC, diff --git a/app/modals/ReleaseNotesModal.tsx b/app/modals/ReleaseNotesModal.tsx index 6abcd8f..c1640b1 100644 --- a/app/modals/ReleaseNotesModal.tsx +++ b/app/modals/ReleaseNotesModal.tsx @@ -52,6 +52,7 @@ const ReleaseNotesModal: FC = ({ ...props }) => { .catch(() => { setLoading(false); }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); let content: JSX.Element; diff --git a/app/model/AnnotationSlice.tsx b/app/model/AnnotationSlice.tsx index 2b0542c..5cbf6c5 100644 --- a/app/model/AnnotationSlice.tsx +++ b/app/model/AnnotationSlice.tsx @@ -29,7 +29,7 @@ const slice = createSlice({ adapter.upsertMany(state, action.payload.annotations); } }, - [deleteEntities.type]: (state, action) => { + [deleteEntities.type]: (state) => { adapter.removeAll(state); }, }, @@ -53,6 +53,8 @@ export const { selectEntities: selectAnnotationEntities, selectAll: selectAllAnnotations, selectTotal: selectTotalAnnotations, -} = adapter.getSelectors((state: any) => state.annotations); +} = adapter.getSelectors( + (state: { annotations: EntityState }) => state.annotations +); export default slice.reducer; diff --git a/app/model/CommentSlice.tsx b/app/model/CommentSlice.tsx index e5bad56..232776a 100644 --- a/app/model/CommentSlice.tsx +++ b/app/model/CommentSlice.tsx @@ -1,5 +1,9 @@ /* eslint-disable import/no-cycle */ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { + createEntityAdapter, + createSlice, + EntityState, +} from '@reduxjs/toolkit'; import CommentEntity from './CommentEntity'; import { loadCorrections, deleteEntities } from './CorrectionsSlice'; @@ -49,6 +53,8 @@ export const { selectEntities: selectCommentsEntities, selectAll: selectAllComments, selectTotal: selectTotalComments, -} = adapter.getSelectors((state: any) => state.comments); +} = adapter.getSelectors( + (state: { comments: EntityState }) => state.comments +); export default slice.reducer; diff --git a/app/model/Correction.tsx b/app/model/Correction.tsx index db080df..5ba3c29 100644 --- a/app/model/Correction.tsx +++ b/app/model/Correction.tsx @@ -6,6 +6,7 @@ import Note from './Note'; import Annotation from './Annotation'; import Submission from './Submission'; import Rating from './Rating'; +import ParserOptions from '../parser/ParserOptions'; type Correction = { id: string; @@ -18,6 +19,7 @@ type Correction = { annotation?: Annotation; timeElapsed?: number; autoCorrectionAttempted?: boolean; + parserOptions?: ParserOptions; }; export default Correction; diff --git a/app/model/CorrectionsSlice.tsx b/app/model/CorrectionsSlice.tsx index 6a558ff..3834678 100644 --- a/app/model/CorrectionsSlice.tsx +++ b/app/model/CorrectionsSlice.tsx @@ -3,6 +3,7 @@ import { createAction, createEntityAdapter, createSlice, + EntityState, } from '@reduxjs/toolkit'; import { normalize } from 'normalizr'; import Correction from './Correction'; @@ -56,7 +57,9 @@ export const { selectEntities: selectCorrectionEntities, selectAll: selectAllCorrections, selectTotal: selectTotalCorrections, -} = adapter.getSelectors((state: any) => state.corrections); +} = adapter.getSelectors( + (state: { corrections: EntityState }) => state.corrections +); export function upsertCorrection(correction: Correction) { return (dispatch) => { diff --git a/app/model/CorrectorSlice.tsx b/app/model/CorrectorSlice.tsx index 3ee56f7..09f7900 100644 --- a/app/model/CorrectorSlice.tsx +++ b/app/model/CorrectorSlice.tsx @@ -29,7 +29,7 @@ const slice = createSlice({ [loadCorrections.type]: (state, action) => { adapter.upsertMany(state, action.payload.correctors); }, - [deleteEntities.type]: (state, action) => { + [deleteEntities.type]: (state) => { adapter.removeAll(state); }, }, @@ -41,7 +41,9 @@ export const { selectEntities: selectCorrectorEntities, selectAll: selectAllCorrectors, selectTotal: selectTotalCorrectors, -} = adapter.getSelectors((state: any) => state.correctors); +} = adapter.getSelectors( + (state: { correctors: EntityState }) => state.correctors +); export const { correctorsAddOne, diff --git a/app/model/CourseSlice.tsx b/app/model/CourseSlice.tsx index 1c72ffd..4b01afb 100644 --- a/app/model/CourseSlice.tsx +++ b/app/model/CourseSlice.tsx @@ -29,7 +29,7 @@ const slice = createSlice({ [loadCorrections.type]: (state, action) => { adapter.upsertMany(state, action.payload.courses); }, - [deleteEntities.type]: (state, action) => { + [deleteEntities.type]: (state) => { adapter.removeAll(state); }, }, @@ -41,7 +41,9 @@ export const { selectEntities: selectCourseEntities, selectAll: selectAllCourses, selectTotal: selectTotalCourses, -} = adapter.getSelectors((state: any) => state.courses); +} = adapter.getSelectors( + (state: { courses: EntityState }) => state.courses +); export const { coursesAddOne, diff --git a/app/model/LocationSlice.tsx b/app/model/LocationSlice.tsx index bdce402..821d657 100644 --- a/app/model/LocationSlice.tsx +++ b/app/model/LocationSlice.tsx @@ -31,7 +31,7 @@ const slice = createSlice({ adapter.upsertMany(state, action.payload.locations); } }, - [deleteEntities.type]: (state, action) => { + [deleteEntities.type]: (state) => { adapter.removeAll(state); }, }, @@ -43,7 +43,9 @@ export const { selectEntities: selectLocationEntities, selectAll: selectAllLocations, selectTotal: selectTotalLocations, -} = adapter.getSelectors((state: any) => state.locations); +} = adapter.getSelectors( + (state: { locations: EntityState }) => state.locations +); export const { locationsAddOne, diff --git a/app/model/NoteSlice.tsx b/app/model/NoteSlice.tsx index 5588fea..e49dca5 100644 --- a/app/model/NoteSlice.tsx +++ b/app/model/NoteSlice.tsx @@ -29,7 +29,7 @@ const slice = createSlice({ adapter.upsertMany(state, action.payload.notes); } }, - [deleteEntities.type]: (state, action) => { + [deleteEntities.type]: (state) => { adapter.removeAll(state); }, }, @@ -53,6 +53,6 @@ export const { selectEntities: selectNoteEntities, selectAll: selectAllNotes, selectTotal: selectTotalNotes, -} = adapter.getSelectors((state: any) => state.notes); +} = adapter.getSelectors((state: { notes: EntityState }) => state.notes); export default slice.reducer; diff --git a/app/model/RatingSlice.tsx b/app/model/RatingSlice.tsx index 5f1c748..db27b55 100644 --- a/app/model/RatingSlice.tsx +++ b/app/model/RatingSlice.tsx @@ -3,7 +3,6 @@ import { createEntityAdapter, createSlice, EntityState, - PayloadAction, } from '@reduxjs/toolkit'; import { loadCorrections, deleteEntities } from './CorrectionsSlice'; import RatingEntity from './RatingEntity'; @@ -32,7 +31,7 @@ const slice = createSlice({ adapter.upsertMany(state, action.payload.ratings); } }, - [deleteEntities.type]: (state, action) => { + [deleteEntities.type]: (state) => { adapter.removeAll(state); }, }, @@ -44,7 +43,9 @@ export const { selectEntities: selectRatingEntities, selectAll: selectAllRatings, selectTotal: selectTotalRatings, -} = adapter.getSelectors((state: any) => state.ratings); +} = adapter.getSelectors( + (state: { ratings: EntityState }) => state.ratings +); export const { ratingsAddOne, diff --git a/app/model/SchoolSlice.tsx b/app/model/SchoolSlice.tsx index 546434f..3587029 100644 --- a/app/model/SchoolSlice.tsx +++ b/app/model/SchoolSlice.tsx @@ -29,7 +29,7 @@ const slice = createSlice({ [loadCorrections.type]: (state, action) => { adapter.upsertMany(state, action.payload.schools); }, - [deleteEntities.type]: (state, action) => { + [deleteEntities.type]: (state) => { adapter.removeAll(state); }, }, @@ -41,7 +41,9 @@ export const { selectEntities: selectSchoolEntities, selectAll: selectAllSchools, selectTotal: selectTotalSchools, -} = adapter.getSelectors((state: any) => state.schools); +} = adapter.getSelectors( + (state: { schools: EntityState }) => state.schools +); export const { schoolsAddOne, diff --git a/app/model/Selectors.ts b/app/model/Selectors.ts index 0072911..e2a92c6 100644 --- a/app/model/Selectors.ts +++ b/app/model/Selectors.ts @@ -13,8 +13,7 @@ import { SheetsSchema, } from './NormalizationSchema'; import Sheet from './Sheet'; -import SheetEntity from './SheetEntity'; -import { selectAllSheets, selectSheetIds } from './SheetSlice'; +import { selectSheetIds } from './SheetSlice'; export const selectAllEntities = createSelector( [ diff --git a/app/model/SettingsSlice.ts b/app/model/SettingsSlice.ts index 5cdd696..f83a890 100644 --- a/app/model/SettingsSlice.ts +++ b/app/model/SettingsSlice.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ParserType } from '../parser/Parser'; +import ParserType from '../parser/ParserType'; import ConditionalComment from './ConditionalComment'; import { Theme } from './Theme'; diff --git a/app/model/SheetSlice.tsx b/app/model/SheetSlice.tsx index 2882b20..55d31e2 100644 --- a/app/model/SheetSlice.tsx +++ b/app/model/SheetSlice.tsx @@ -1,5 +1,9 @@ /* eslint-disable import/no-cycle */ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { + createEntityAdapter, + createSlice, + EntityState, +} from '@reduxjs/toolkit'; import { loadCorrections, deleteEntities } from './CorrectionsSlice'; import SheetEntity from './SheetEntity'; @@ -50,4 +54,6 @@ export const { selectEntities: selectSheetEntities, selectAll: selectAllSheets, selectTotal: selectTotalSheets, -} = adapter.getSelectors((state: any) => state.sheets); +} = adapter.getSelectors( + (state: { sheets: EntityState }) => state.sheets +); diff --git a/app/model/SubmissionSlice.tsx b/app/model/SubmissionSlice.tsx index 5135de8..c7478fd 100644 --- a/app/model/SubmissionSlice.tsx +++ b/app/model/SubmissionSlice.tsx @@ -1,5 +1,9 @@ /* eslint-disable import/no-cycle */ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { + createEntityAdapter, + createSlice, + EntityState, +} from '@reduxjs/toolkit'; import { loadCorrections, deleteEntities } from './CorrectionsSlice'; import SubmissionEntity from './SubmissionEntity'; @@ -48,4 +52,6 @@ export const { selectEntities: selectSubmissionEntities, selectAll: selectAllSubmissions, selectTotal: selectTotalSubmissions, -} = adapter.getSelectors((state: any) => state.submissions); +} = adapter.getSelectors( + (state: { submissions: EntityState }) => state.submissions +); diff --git a/app/model/TaskSlice.tsx b/app/model/TaskSlice.tsx index 815c6c1..84e1101 100644 --- a/app/model/TaskSlice.tsx +++ b/app/model/TaskSlice.tsx @@ -2,6 +2,7 @@ import { createEntityAdapter, createSlice, + EntityState, PayloadAction, } from '@reduxjs/toolkit'; import { loadCorrections, deleteEntities } from './CorrectionsSlice'; @@ -46,7 +47,7 @@ const slice = createSlice({ }, */ }, extraReducers: { - [loadCorrections.type]: (state: any, action) => { + [loadCorrections.type]: (state: EntityState, action) => { if (action.payload.tasks !== undefined) { adapter.upsertMany(state, action.payload.tasks); } @@ -105,4 +106,6 @@ export const { selectEntities: selectTaskEntities, selectAll: selectAllTasks, selectTotal: selectTotalTasks, -} = adapter.getSelectors((state: any) => state.tasks); +} = adapter.getSelectors( + (state: { tasks: EntityState }) => state.tasks +); diff --git a/app/model/TermSlice.tsx b/app/model/TermSlice.tsx index f264632..929949e 100644 --- a/app/model/TermSlice.tsx +++ b/app/model/TermSlice.tsx @@ -29,7 +29,7 @@ const slice = createSlice({ [loadCorrections.type]: (state, action) => { adapter.upsertMany(state, action.payload.terms); }, - [deleteEntities.type]: (state, action) => { + [deleteEntities.type]: (state) => { adapter.removeAll(state); }, }, @@ -41,7 +41,7 @@ export const { selectEntities: selectTermEntities, selectAll: selectAllTerms, selectTotal: selectTotalTerms, -} = adapter.getSelectors((state: any) => state.terms); +} = adapter.getSelectors((state: { terms: EntityState }) => state.terms); export const { termsAddOne, diff --git a/app/package.json b/app/package.json index 99f98f2..d501685 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "name": "correctinator", "productName": "correctinator", - "version": "1.4.4", + "version": "1.4.5", "description": "A correction program with media viewer for Uni2Work rating files", "main": "./main.prod.js", "author": { diff --git a/app/parser/Parser.ts b/app/parser/Parser.ts index 4f1ef5f..dc34e5c 100644 --- a/app/parser/Parser.ts +++ b/app/parser/Parser.ts @@ -1,12 +1,9 @@ import Correction from '../model/Correction'; - -export enum ParserType { - Uni2Work = 'UNI2WORK', -} +import ParserType from './ParserType'; export default interface Parser { configFilePattern: RegExp; - deserialize(text: string, dirName: string): Correction; + deserialize(text: string, dirName: string, fileName: string): Correction; serialize(correction: Correction, tasksAndComments?: string): string; getConfigFileName(correction: Correction): string; getType(): ParserType; diff --git a/app/parser/ParserOptions.ts b/app/parser/ParserOptions.ts new file mode 100644 index 0000000..9a571fc --- /dev/null +++ b/app/parser/ParserOptions.ts @@ -0,0 +1,9 @@ +import ParserType from './ParserType'; + +type ParserOptions = { + type: ParserType; + language: string; + fileName: string; +}; + +export default ParserOptions; diff --git a/app/parser/ParserType.ts b/app/parser/ParserType.ts new file mode 100644 index 0000000..49eb682 --- /dev/null +++ b/app/parser/ParserType.ts @@ -0,0 +1,4 @@ +enum ParserType { + Uni2Work = 'UNI2WORK', +} +export default ParserType; diff --git a/app/parser/ParserUtil.ts b/app/parser/ParserUtil.ts index 4e065f2..78d2216 100644 --- a/app/parser/ParserUtil.ts +++ b/app/parser/ParserUtil.ts @@ -1,4 +1,5 @@ -import Parser, { ParserType } from './Parser'; +import Parser from './Parser'; +import ParserType from './ParserType'; import Uni2WorkParser from './Uni2WorkParser'; const instanciateParser = (type: ParserType): Parser => { diff --git a/app/parser/Uni2WorkParser.test.ts b/app/parser/Uni2WorkParser.test.ts index 1c9d173..38c5ded 100644 --- a/app/parser/Uni2WorkParser.test.ts +++ b/app/parser/Uni2WorkParser.test.ts @@ -1,6 +1,7 @@ import Correction from '../model/Correction'; import Status from '../model/Status'; import Parser from './Parser'; +import ParserType from './ParserType'; import Uni2WorkParser, { Uni2WorkDataStructure } from './Uni2WorkParser'; const parser: Parser = new Uni2WorkParser(); @@ -31,6 +32,32 @@ rating_done: false # TODO: Von false auf true setzen, sobald Bewertung abgeschlo # TODO: Korrektur-Kommentar für die Studierenden unterhalb der Abtrennung (...) eintragen ...`; +const u2wTestString4 = ` +%YAML 1.2 +--- +# Meta-Informationen zur Korrektur (werden beim Hochladen ignoriert) +term: Winter 2021/22 +school: Institut für Informatik +course: Rechnerarchitektur +sheet: + name: Online-Hausarbeit 5 + type: normal + grading: + max: 10 + type: points +rated_by: John Doe +rated_at: null + +# Abgabenummer; wird beim Hochladen mit dem Dateinamen abgeglichen +submission: uwazxvya2akrnnc2 + +# Bewertung +points: null # TODO: Hier die Punktezahl statt null eintragen (bis zu zwei Nachkommastellen, Punkt als Dezimalseparator; z.B. 17.03) +rating_done: false # TODO: Von false auf true setzen, sobald Bewertung abgeschlossen; sonst Korrektur für die Studierenden nicht sichtbar und keine Anrechnung auf Prüfungsbonus + +# TODO: Korrektur-Kommentar für die Studierenden unterhalb der Abtrennung (...) eintragen +...`; + const u2wTestData1: Uni2WorkDataStructure = { term: 'SoSe 2020', school: 'Institut für Informatik', @@ -84,9 +111,59 @@ const correctionTestData1: Correction = { }, // note: { text: '' }, // annotation: { text: '' }, + parserOptions: { + fileName: 'bewertung_uwazxvya2akrnnc2.txt', + language: 'de', + type: ParserType.Uni2Work, + }, }; correctionTestData1.submission.correction = correctionTestData1; +const correctionTestData4: Correction = { + id: '5475c708-3ced-5ff5-ad51-a1c12f8a2757', + submission: { + id: 'd519b90c-6b6e-5d67-9e0d-318f05693b01', + name: 'uwazxvya2akrnnc2', + sheet: { + id: 'cbce52d6-5bba-599d-ba60-273d23ef3d2e', + name: 'Online-Hausarbeit 5', + type: 'normal', + maxValue: 10, + valueType: 'points', + school: { + id: '344bd582-e110-53f6-a10c-6a14d6a7e291', + name: 'Institut für Informatik', + }, + term: { + id: '2bb8000f-5fed-5a2e-a43d-a0435b3476d8', + year: 2021, + summerterm: false, + }, + course: { + id: '73ae54b7-afdd-5912-a5d0-a1dd488fe912', + name: 'Rechnerarchitektur', + }, + }, + }, + corrector: { + id: 'b3aa67d2-4ae3-5441-860a-88ab5391673d', + name: 'John Doe', + }, + status: Status.Todo, + location: { + id: '148bdfa9-7596-5319-a197-ead64880df40', + name: null, + }, + // note: { text: '' }, + // annotation: { text: '' }, + parserOptions: { + fileName: 'rating_uwazxvya2akrnnc2.txt', + language: 'en', + type: ParserType.Uni2Work, + }, +}; +correctionTestData4.submission.correction = correctionTestData4; + const u2wTestString2 = `term: SoSe 2020 school: Institut für Informatik course: Rechnerarchitektur @@ -339,6 +416,40 @@ test('serializeTerm WiSe 2020/21', () => { ).toBe(u2wTestData2.term); }); +test('serializeTerm SoSe 2021 eng', () => { + expect( + Uni2WorkParser.serializeTerm( + { + id: 'b91bb5ff-1141-53f7-96b5-2cafc53ce6ea', + year: 2021, + summerterm: true, + }, + { + fileName: 'rating_uwazxvya2akrnnc2.txt', + language: 'en', + type: ParserType.Uni2Work, + } + ) + ).toBe('Summer 2021'); +}); + +test('serializeTerm WiSe 2020/21 eng', () => { + expect( + Uni2WorkParser.serializeTerm( + { + id: 'cc47b29b-a507-533f-b4db-1fee84ed81fb', + year: 2021, + summerterm: false, + }, + { + fileName: 'rating_uwazxvya2akrnnc2.txt', + language: 'en', + type: ParserType.Uni2Work, + } + ) + ).toBe('Winter 2021/22'); +}); + test('deserializeTerm SoSe 2020', () => { expect(Uni2WorkParser.deserializeTerm(u2wTestData1.term)).toStrictEqual({ id: 'b91bb5ff-1141-53f7-96b5-2cafc53ce6ea', @@ -355,6 +466,22 @@ test('deserializeTerm WiSe 2020/21', () => { }); }); +test('deserializeTerm SoSe 2021 eng', () => { + expect(Uni2WorkParser.deserializeTerm('Summer 2021')).toStrictEqual({ + id: '230fe0c4-e9cc-5515-9101-2449f3b96dec', + year: 2021, + summerterm: true, + }); +}); + +test('deserializeTerm WiSe 2021/22 eng', () => { + expect(Uni2WorkParser.deserializeTerm('Winter 2021/22')).toStrictEqual({ + id: '2bb8000f-5fed-5a2e-a43d-a0435b3476d8', + year: 2021, + summerterm: false, + }); +}); + // Status test('deserializeStatus with rating_done false', () => { @@ -417,9 +544,23 @@ test('deserializeSchool Institut für Informatik', () => { // Correction test('deserialize Correction u2wTestData1', () => { - expect(parser.deserialize(u2wTestString1, 'uwazxvya2akrnnc2')).toMatchObject( - correctionTestData1 - ); + expect( + parser.deserialize( + u2wTestString1, + 'uwazxvya2akrnnc2', + 'bewertung_uwazxvya2akrnnc2.txt' + ) + ).toMatchObject(correctionTestData1); +}); + +test('deserialize Correction u2wTestData4', () => { + expect( + parser.deserialize( + u2wTestString4, + 'uwazxvya2akrnnc2', + 'rating_uwazxvya2akrnnc2.txt' + ) + ).toMatchObject(correctionTestData4); }); test('serialize correctionTestData2', () => { diff --git a/app/parser/Uni2WorkParser.ts b/app/parser/Uni2WorkParser.ts index 0cc8f1b..dc5ed2e 100644 --- a/app/parser/Uni2WorkParser.ts +++ b/app/parser/Uni2WorkParser.ts @@ -1,6 +1,6 @@ /* eslint-disable class-methods-use-this */ import * as YAML from 'yaml'; -import Parser, { ParserType } from './Parser'; +import Parser from './Parser'; import UUID from '../utils/UUID'; import Term from '../model/Term'; import Correction from '../model/Correction'; @@ -10,6 +10,8 @@ import Location from '../model/Location'; import Course from '../model/Course'; import School from '../model/School'; import Rating from '../model/Rating'; +import ParserOptions from './ParserOptions'; +import ParserType from './ParserType'; export type Uni2WorkDataStructure = { term: string; @@ -39,10 +41,14 @@ export type Uni2WorkDataStructure = { }; export default class Uni2WorkParser implements Parser { - public configFilePattern = /bewertung_([a-z0-9]{16})\.txt/g; - - public deserialize(text: string, dirName: string): Correction { - const [configText, ...rest] = text.split('...'); + public configFilePattern = /(bewertung|rating)_([a-z0-9]{16})\.txt/g; + + public deserialize( + text: string, + dirName: string, + fileName: string + ): Correction { + const configText = text.split('...')[0]; const u2wDoc = YAML.parseDocument(configText); if (u2wDoc.errors.length > 0) { @@ -86,6 +92,11 @@ export default class Uni2WorkParser implements Parser { corrector: Uni2WorkParser.deserializeCorrector(u2wData.rated_by), status: Uni2WorkParser.deserializeStatus(u2wData.rating_done), location: Uni2WorkParser.deserializeLocation(u2wData.rated_at), + parserOptions: { + type: ParserType.Uni2Work, + language: fileName.includes('rating') ? 'en' : 'de', + fileName, + }, }; // Exam Sheet @@ -105,8 +116,8 @@ export default class Uni2WorkParser implements Parser { public static deserializeTerm(term: string): Term { // More detailed: 1. WiSe|SoSe 2. Century e.g. 20 3. Year start 4. Year end (only WiSe) // const termPattern = /(wise|sose)\s*(\d{2})(\d{2})(?:\/(\d{2}))?/gi; - const termPattern = /(wise|sose)\s*(\d{4})/i; - const summertermPattern = /sose/gi; + const termPattern = /(wise|sose|Winter|Summer)\s*(\d{4})/i; + const summertermPattern = /sose|Summer/gi; const termGroups = term.match(termPattern); if (termGroups === null) { throw new Error(`Could not parse term!`); @@ -121,8 +132,16 @@ export default class Uni2WorkParser implements Parser { }; } - public static serializeTerm(term: Term): string { - return `${term.summerterm ? 'SoSe' : 'WiSe'} ${term.year}${ + public static serializeTerm(term: Term, options?: ParserOptions): string { + let summerterm = 'SoSe'; + let winterterm = 'WiSe'; + + if (options?.language === 'en') { + summerterm = 'Summer'; + winterterm = 'Winter'; + } + + return `${term.summerterm ? summerterm : winterterm} ${term.year}${ term.summerterm ? '' : `/${String(term.year + 1).slice(-2)}` }`; } @@ -165,7 +184,10 @@ export default class Uni2WorkParser implements Parser { public serialize(correction: Correction, tasksAndComments = ''): string { const u2wData: Uni2WorkDataStructure = { - term: Uni2WorkParser.serializeTerm(correction.submission.sheet.term), + term: Uni2WorkParser.serializeTerm( + correction.submission.sheet.term, + correction.parserOptions + ), school: correction.submission.sheet.school.name, course: correction.submission.sheet.course.name, sheet: { @@ -199,7 +221,10 @@ export default class Uni2WorkParser implements Parser { } getConfigFileName(correction: Correction): string { - return `bewertung_${correction.submission.name}.txt`; + return ( + correction.parserOptions?.fileName || + `bewertung_${correction.submission.name}.txt` + ); } getType(): ParserType { diff --git a/app/store.ts b/app/store.ts index 15d3598..656b33a 100644 --- a/app/store.ts +++ b/app/store.ts @@ -33,6 +33,7 @@ const persistConfig = { export const history = createHashHistory(); const combinedReducer = createRootReducer(history); +// eslint-disable-next-line @typescript-eslint/no-explicit-any const rootReducer: any = persistReducer(persistConfig, combinedReducer); export type RootState = ReturnType; @@ -100,4 +101,4 @@ export const configuredStore = (initialState?: RootState) => { export type Store = ReturnType; export type AppThunk = ThunkAction>; // eslint-disable-next-line import/prefer-default-export -export const useAppDispatch = () => useDispatch(); +export const useAppDispatch = () => useDispatch(); diff --git a/app/utils/TaskUtil.ts b/app/utils/TaskUtil.ts index bbfe561..de77780 100644 --- a/app/utils/TaskUtil.ts +++ b/app/utils/TaskUtil.ts @@ -35,19 +35,21 @@ export function getTaskType(task: Task | TaskEntity): TaskType { return TaskType.Parent; } -export function hasTasksWithZeroMax(ts: Task[] | TaskEntity[]): boolean { - const test: Task | TaskEntity = (ts as any).find((t: Task | TaskEntity) => { - if (isRateableTask(t)) { - if (t.max === 0) { - return true; - } - } else if (isSingleChoiceTask(t)) { - if (t.answer.value === 0) { - return true; +export function hasTasksWithZeroMax(ts: (Task | TaskEntity)[]): boolean { + const test: Task | TaskEntity | undefined = ts.find( + (t: Task | TaskEntity) => { + if (isRateableTask(t)) { + if (t.max === 0) { + return true; + } + } else if (isSingleChoiceTask(t)) { + if (t.answer.value === 0) { + return true; + } } + return false; } - return false; - }); + ); return test !== undefined; } @@ -72,22 +74,6 @@ export function getTopLevelTasks(tasks: Task[]): Task[] { return top; } -export function removeTaskIds(tasks: Task[], res: any = []) { - tasks.forEach((t) => { - if (isParentTask(t)) { - const { id, ...rest } = t; - const temp: any = { ...rest }; - temp.tasks = removeTaskIds(t.tasks); - res.push(temp); - } else { - const { id, ...rest } = t; - const temp = { ...rest }; - res.push(temp); - } - }); - return res; -} - export function flatMapTask(pt: ParentTask, list: Task[] = []) { list.push(pt); pt.tasks.forEach((t) => { diff --git a/package.json b/package.json index fea2c5e..d944bb7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "correctinator", "productName": "correctinator", - "version": "1.4.4", + "version": "1.4.5", "description": "A correction program with media viewer for Uni2Work rating files", "scripts": { "build": "concurrently \"yarn build-main\" \"yarn build-renderer\"",