Skip to content

Commit

Permalink
Document everything
Browse files Browse the repository at this point in the history
Add typedoc doc-comments to everything non-boilerplate that's missing
them.
Lint some recently added code.
  • Loading branch information
Hal-9k1 committed Jul 15, 2024
1 parent 02984af commit c6bc0ca
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 32 deletions.
22 changes: 20 additions & 2 deletions src/common/AppConsoleMessage.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
/**
* A type of AppConsoleMessage.
*/
export type MessageType = 'dawn-err' | 'robot-err' | 'robot-info';

/**
* Represents a formatted message in the AppConsole.
*/
export default class AppConsoleMessage {
#type: string;
/**
* The type of the message.
*/
#type: MessageType;

/**
* The text content of the message.
*/
#text: string;

constructor(type: string, text: string) {
/**
* @param type - the type of the message
* @param text - the text content of the message
*/
constructor(type: MessageType, text: string) {
this.#type = type;
this.#text = text;
}
Expand Down
102 changes: 84 additions & 18 deletions src/main/MainApp.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,57 @@
import { dialog, ipcMain } from 'electron';
import type { BrowserWindow } from 'electron';
import type { BrowserWindow, FileFilter } from 'electron';
import fs from 'fs';
import { version as dawnVersion } from '../../package.json';
import AppConsoleMessage from '../common/AppConsoleMessage';

/**
* Cooldown time in milliseconds to wait between sending didExternalChange messages to the renderer
* process.
*/
const WATCH_DEBOUNCE_MS = 3000;
const CODE_FILE_FILTERS = [
/**
* Electron file filters for the save and open dialogs.
*/
const CODE_FILE_FILTERS: FileFilter[] = [
{ name: 'Python Files', extensions: ['py'] },
{ name: 'All Files', extensions: ['*'] },
];

/**
* Manages state owned by the main electron process.
*/
export default class MainApp {
/**
* The BrowserWindow.
*/
readonly #mainWindow: BrowserWindow;

/**
* The last saved to or opened path of the student code file.
*/
#savePath: string | null;

/**
* A file watcher that watches the last saved path to detect external changes to the currently
* open file.
*/
#watcher: fs.FSWatcher | null;

/**
* Whether the file watcher is currently sensitive to watch events. Watch events trigger a
* cooldown so spurious didExternalChange messages aren't sent to the renderer process.
*/
#watchDebounce: boolean;

/**
* Whether the next attempt to close the main window should instead notify the renderer and expect
* a return event if this is ok.
*/
#preventQuit: boolean;

/**
* @param mainWindow - the BrowserWindow.
*/
constructor(mainWindow: BrowserWindow) {
this.#mainWindow = mainWindow;
this.#savePath = null;
Expand All @@ -30,7 +61,7 @@ export default class MainApp {
mainWindow.on('close', (e) => {
if (this.#preventQuit) {
e.preventDefault();
this.#sendToRenderer('renderer-quit-request');
this.#mainWindow.webContents.send('renderer-quit-request');
}
});
ipcMain.on('main-file-control', (_event, data) => {
Expand All @@ -49,27 +80,47 @@ export default class MainApp {
});
}

#sendToRenderer(...args) {
this.#mainWindow.webContents.send(...args);
}

/**
* Lifecycle method to be called when the renderer process has finished initializing. This may
* happen more than once in the program's lifetime (e.g. if the BrowserWindow is somehow
* reloaded).
*/
onPresent() {
this.#watcher?.close();
this.#savePath = null;
this.#sendToRenderer('renderer-init', { dawnVersion });
this.#mainWindow.webContents.send('renderer-init', { dawnVersion });
}

/**
* Requests that the renderer process start to save the code in the editor.
* @param forceDialog - whether the save path selection dialog should be shown even if there is
* a currently opened file that may be saved to.
*/
promptSaveCodeFile(forceDialog: boolean) {
this.#sendToRenderer('renderer-file-control', {
// We need a round trip to the renderer because that's where the code in the editor actually
// lives
this.#mainWindow.webContents.send('renderer-file-control', {
type: 'promptSave',
forceDialog,
});
}

/**
* Requests that the renderer process start to load code from a file into the editor. The renderer
* may delay or ignore this request (e.g. if the file currently beind edited is dirty).
*/
promptLoadCodeFile() {
this.#sendToRenderer('renderer-file-control', { type: 'promptLoad' });
this.#mainWindow.webContents.send('renderer-file-control', {
type: 'promptLoad',
});
}

/**
* Tries to save code to a file. Fails if the user is prompted for a path but does not enter one.
* @param code - the code to save
* @param forceDialog - whether the user should be prompted for a save path even if there is a
* currently open file.
*/
#saveCodeFile(code: string, forceDialog: boolean) {
let success = true;
if (this.#savePath === null || forceDialog) {
Expand All @@ -80,40 +131,51 @@ export default class MainApp {
this.#watcher?.close();
this.#watcher = null;
fs.writeFile(
this.#savePath,
this.#savePath as string,
code,
{ encoding: 'utf8', flag: 'w' },
(err) => {
if (err) {
this.#sendToRenderer(
this.#mainWindow.webContents.send(
'renderer-post-console',
new AppConsoleMessage(
'dawn-err',
`Failed to save code to ${this.#savePath}. ${err}`,
),
);
} else {
this.#sendToRenderer('renderer-file-control', { type: 'didSave' });
this.#mainWindow.webContents.send('renderer-file-control', {
type: 'didSave',
});
}
this.#watchCodeFile();
},
);
}
}

/**
* Tries to load code from a file into the editor. Fails is the user does not choose a path.
*/
#openCodeFile() {
const success = this.#showCodePathDialog('load');
if (success) {
this.#sendToRenderer('renderer-file-control', {
this.#mainWindow.webContents.send('renderer-file-control', {
type: 'didOpen',
content: fs.readFileSync(this.#savePath, {
content: fs.readFileSync(this.#savePath as string, {
encoding: 'utf8',
flag: 'r',
}),
});
}
}

/**
* Shows an open or save dialog to the user to select a new save path. On success, this.#savePath
* is known to be non-null and non-empty.
* @param mode - the type of dialog that should be shown
* @returns Whether a new path was chosen successfully.
*/
#showCodePathDialog(mode: 'save' | 'load') {
let result: string | string[] | undefined;
if (mode === 'save') {
Expand All @@ -129,7 +191,7 @@ export default class MainApp {
}
if (result && result.length) {
this.#savePath = typeof result === 'string' ? result : result[0];
this.#sendToRenderer('renderer-file-control', {
this.#mainWindow.webContents.send('renderer-file-control', {
type: 'didChangePath',
path: this.#savePath,
});
Expand All @@ -141,11 +203,15 @@ export default class MainApp {
return false;
}

/**
* Attaches a file watcher to listen for external changes to the last saved or loaded code path,
* destroying the previous one if it exists.
*/
#watchCodeFile() {
this.#watcher?.close();
this.#watchDebounce = true;
this.#watcher = fs.watch(
this.#savePath,
this.#savePath as string,
{ persistent: false, encoding: 'utf8' },
() => {
// Don't care what the event type is
Expand All @@ -154,7 +220,7 @@ export default class MainApp {
setTimeout(() => {
this.#watchDebounce = true;
}, WATCH_DEBOUNCE_MS);
this.#sendToRenderer('renderer-file-control', {
this.#mainWindow.webContents.send('renderer-file-control', {
type: 'didExternalChange',
});
}
Expand Down
8 changes: 4 additions & 4 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,13 +238,13 @@ export default function App() {

const handleConnectionChange = (event: ChangeEvent<HTMLInputElement>) => {
const { id, value } = event.target;
if (id == 'IPAddress') {
if (id === 'IPAddress') {
setIPAddress(value);
} else if (id == 'SSHAddress') {
} else if (id === 'SSHAddress') {
setSSHAddress(value);
} else if (id == 'FieldIPAddress') {
} else if (id === 'FieldIPAddress') {
setFieldIPAddress(value);
} else if (id == 'FieldStationNum') {
} else if (id === 'FieldStationNum') {
setFieldStationNum(value);
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/AppConsole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import './AppConsole.css';

/**
* Component displaying output and error messages from student code ran on the robot.
* @param props - props
* @param props.messages - array of console messages to display
*/
export default function AppConsole({
// eslint-disable-next-line
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/ConnectionConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/**
* Button component that opens the ConnectionConfigModal.
* @param props - props
* @param props.onModalOpen - click handler for the button that opens the ConnectionConfigModal
*/
export default function ConnectionConfig({
onModalOpen,
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/ResizeBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import './ResizeBar.css';

/**
* Component allowing Editor to be resized.
* @param props - props
* @param props.onStartResize - handler called when the user initiates a resize
* @param props.onEndResize - handler called when the user ends a resize
* @param props.axis - the axis the bar moves along
*/
export default function ResizeBar({
onStartResize = () => {},
Expand All @@ -11,6 +15,10 @@ export default function ResizeBar({
axis,
}: {
onStartResize: () => void;
/**
* handler called when the bar is dragged
* @param pos - the signed number of pixels the bar has been moved since the start of the resize
*/
onUpdateResize: (pos: number) => boolean;
onEndResize: () => void;
axis: 'x' | 'y';
Expand Down
10 changes: 10 additions & 0 deletions src/renderer/Topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ import './Topbar.css';

/**
* Component displaying Dawn version and connection info.
* @param props - props
* @param props.onConnectionConfigModalOpen - handler called when the ConnectionConfigModal should
* be opened
* @param props.robotLatencyMs - latency in milliseconds of the connection to the currently
* connected robot, or -1 if there is no robot connected
* @param props.runtimeVersion - version string of runtime running on currently connected robot. The
* value is not used if robotLatencyMs is -1
* @param props.robotBatteryVoltage - battery voltage in volts of the currently connected robot. The
* value is not used if robotLatencyMs is -1
* @param props.dawnVersion - version string of Dawn
*/
export default function Topbar({
onConnectionConfigModalOpen,
Expand Down
9 changes: 9 additions & 0 deletions src/renderer/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const STATUS_TEXT = {
};
/**
* Component holding the Ace editor and editor toolbar.
* @param props - props
* @param props.width - width in pixels of Editor container
* @param props.fileStatus - dirty status of the currently open file
* @param props.filePath - path of the currently open file
* @param props.content - the content that should be displayed in the code editor
*/
export default function Editor({
width,
Expand All @@ -25,6 +30,10 @@ export default function Editor({
content,
}: {
width: number;
/**
* change handler for the content of the code editor
* @param content - the new content of the code editor
*/
onChange: (content: string) => void;
fileStatus: 'clean' | 'dirty' | 'extDirty';
filePath: string;
Expand Down
10 changes: 10 additions & 0 deletions src/renderer/modals/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import Modal from './Modal';

/**
* Generic confirmation modal component.
* @param props - props
* @param props.onClose - handler called when the modal is closed by any means
* @param props.onConfirm - handler called when the confirm button is clicked
* @param props.isActive - whether to display the modal
* @param props.queryText - question text in the modal
* @param props.modalTitle - displayed title of the modal
* @param props.noAutoClose - whether the modal should not call onClose after confirmation
*/
export default function ConfirmModal({
onClose,
onConfirm,
Expand Down
Loading

0 comments on commit c6bc0ca

Please sign in to comment.