Added scenery loaded dialog

+Added a dialog shown after scenery parsing is complete, displaying whether any warnings/errors occurred and, if so, showing the parser logs
*Improved parser's error handling
*Fixed a typo in the switch parsing related warning
This commit is contained in:
izawartka 2025-06-09 22:38:49 +02:00
parent 9419f613be
commit c56f471d65
8 changed files with 145 additions and 8 deletions

View File

@ -0,0 +1,14 @@
.scenery-loaded-dialog-content {
display: flex;
flex-direction: column;
gap: 1em;
}
.scenery-loaded-dialog-content .main-message {
padding: 0.5em;
}
.scenery-loaded-dialog-content .scenery-parser-log-view {
max-height: 30vh;
overflow-y: auto;
}

View File

@ -0,0 +1,46 @@
import { forwardRef, useImperativeHandle } from 'react';
import SceneryParserLog from '../../model/scenery-parser-log';
import SceneryParserLogView from '../scenery-loaded-dialog/SceneryParserLogView';
import '../dialog/Dialog.css';
import './SceneryLoadedDialog.css'
import Constants from '../../helpers/constants';
const SceneryLoadedDialog = forwardRef((props, ref) => {
const {onClose, loadingError} = props;
const onBgClick = () => {
onClose();
}
useImperativeHandle(ref, () => ({
onBgClick: onBgClick
}));
const warningsOrError = SceneryParserLog.hasWarnings || !!loadingError;
const showLog = Constants.parser.alwaysShowLogDialog || warningsOrError;
const message = loadingError ? "An error occurred while loading the scenery"
:( SceneryParserLog.hasWarnings ? "Scenery loaded with warnings"
: "Scenery loaded successfully" );
const additionalErr = (loadingError && !SceneryParserLog.hasErrors) ?
loadingError.message : undefined;
return (
<div className='dialog wide'>
<div className='dialog-content scenery-loaded-dialog-content'>
<div className='main-message'>
{message}
</div>
{showLog && <SceneryParserLogView log={SceneryParserLog.getLog()} additionalErr={additionalErr} />}
</div>
<div className='dialog-buttons'>
<button onClick={() => onClose()} autoFocus={true}>
Ok
</button>
</div>
</div>
);
});
export default SceneryLoadedDialog;

View File

@ -0,0 +1,22 @@
.scenery-parser-log-view {
padding: 1em;
border-radius: 8px;
border: 2px solid #444;
background-color: #0004;
}
.scenery-parser-log-view ul {
list-style-type: none;
padding: 0;
margin: 0;
text-indent: -1em;
padding-left: 1em;
}
.scenery-parser-log-view .log-entry.warn {
color: #aa4;
}
.scenery-parser-log-view .log-entry.error {
color: #f44;
}

View File

@ -0,0 +1,27 @@
import './SceneryParserLogView.css';
export default function SceneryParserLogView({ log, additionalErr }) {
const extendedLog = additionalErr ? [...log, { logType: 'error', message: additionalErr }] : log;
return (
<div className="scenery-parser-log-view">
{extendedLog.length === 0 ? (
<div className="no-log">No log entries.</div>
) : (
<ul className="log-list">
{extendedLog.map((entry, index) => <LogEntry entry={entry} key={index} />)}
</ul>
)}
</div>
);
}
function LogEntry({ entry }) {
const { logType, message } = entry;
return (
<li className={`log-entry ${logType}`}>
{message}
</li>
);
}

View File

@ -1,6 +1,9 @@
import { useContext } from "react";
import MainContext from "../../contexts/MainContext";
import Scenery from "../../model/scenery";
import { showCustomDialog } from "../../services/dialogService";
import SceneryLoadedDialog from "../scenery-loaded-dialog/SceneryLoadedDialog";
import SceneryParserLog from "../../model/scenery-parser-log";
export default function FileSelect() {
const {setScenery, setIsLoading} = useContext(MainContext);
@ -9,17 +12,22 @@ export default function FileSelect() {
const file = event.target.files[0];
if (!file) return;
SceneryParserLog.clear();
setIsLoading(true);
const reader = new FileReader();
reader.readAsText(file);
reader.onload = (e) => {
setIsLoading(false);
if(!e.target || !e.target.result) {
console.error("File reading failed or no content found.");
let loadingError = null;
try {
const scenery = Scenery.fromText(e.target?.result);
setScenery(scenery);
} catch (error) {
loadingError = error;
setScenery(null);
}
const scenery = Scenery.fromText(e.target.result);
setScenery(scenery);
setIsLoading(false);
showCustomDialog(<SceneryLoadedDialog loadingError={loadingError} />);
}
};

View File

@ -6,7 +6,8 @@ const Constants = {
parser: {
forceAutoSwitches: false,
logNewAutoSwitches: true,
sceneryInfoVersion: 29
sceneryInfoVersion: 29,
alwaysShowLogDialog: false
},
warnings: {
all: false, // enable all warnings

View File

@ -1,13 +1,32 @@
import Constants from "../helpers/constants";
export default class SceneryParserLog {
static log = [];
static hasWarnings = false;
static hasErrors = false;
static clear() {
SceneryParserLog.log = [];
SceneryParserLog.hasWarnings = false;
SceneryParserLog.hasErrors = false;
}
static warn(type, message) {
if(!Constants.warnings[type] && !Constants.warnings.all) return;
SceneryParserLog.hasWarnings = true;
SceneryParserLog.log.push({logType: 'warn', type, message});
console.warn(`[SceneryParserLog] ${message}`);
}
static error(type, message) {
SceneryParserLog.hasErrors = true;
SceneryParserLog.log.push({logType: 'error', type, message});
throw new Error(`[SceneryParserLog] ${message}`);
}
static getLog() {
return [...SceneryParserLog.log];
}
}

View File

@ -63,7 +63,7 @@ export default class Switch extends SceneryObject {
let def = DefinedSwitches[this.bare_model] || null;
if(!def && !Constants.parser.forceAutoSwitches) {
SceneryParserLog.warn('switchUndefinedModel', `Switch ${this.id} has undefined model "${this.bare_model}", trying to generate auto model definition`);
SceneryParserLog.warn('switchUndefinedModel', `Switch ${this.id} has an undefined model "${this.bare_model}", trying to generate auto model definition`);
}
if(!def || Constants.parser.forceAutoSwitches) {