Project Categories
Project Setting | Personal |
Team Size | 1 |
Role(s) | Creator / Developer |
Languages | C# HTML CSS JavaScript TypeScript |
Software/Tools Used | Visual Studio Code, Node, Electron, Node.js API, React, React Router DOM, SASS, Webpack, Babel, Material UI, Tippy.js |
Status | In Development |
Time Period | March 2020 - Current |
About
Project Archivist was created for two reasons: 1. to create something useful in under 24-hours in an almost Hackathon/Game-jam-esque fashion; 2. to give a visual interface for creating scripts for creating backups. Not only am I a very visual/interface-oriented person, creating scripts can be a bit problematic. That is, one could make a typo later in a script and not discover it until encountering an error as late as hours later when considering the length of time backups can take.
This first was created using Windows Forms. The program serves its purpose and accounts for a number of use/error-cases. However, due to the visual and platform limitations of Windows Forms, I decided to migrate this program into Electron and archive the Windows Forms version. I used this as an opportunity to learn both a new API (Electron, + Node.js by inheritance). Due to initial complications with TypeScript, I stuck with traditional JavaScript for this project
Following an internship with Forbes, I was inspired to make another attempt at using TypeScript and Sass while also learning React (the remaining of the big three front-end frameworks that I hadn't worked with). I forked a popular repo called the electron react boilerplate to make my own that was a bit more functional to my needs, then used it to generate the new Archivist repo. This iteration of the project is still under development.
My Work
WinForms
- Design main, item, exclusion, error, and message windows for the program to use
- Set up form elements in a semantic way (limitations on numeric up-downs, using the proper file/folder dialogs)
- Make use of an ArchivedItem object to use information from the forms in setting up the output script
- Handle a number of error cases to prevent the user from potentially making a mistake
Electron JS
- Design a single-window interface using HTML/CSS markup/styling, with multiple stylesheets to serve different purposes and to allow for future theming capabilities
- Make use of JQuery selectors/events for buttons and form elements
- Make use of Node.js File System module for file writing
- Make use of Tippy.js for tooltips for form controls
- Re-create ArchivedItem and similar structures from the WinForms application
- Reach mirrored functionality with WinForms app for a 1.0.0 full release
- Resolve numerous bugs found post-release
Electron React
- Forked and modified a boilerplate first used for generating the Archivist repo
- Utilize Material UI to create re-usable UI inputs
- Create class-based components in a nested separation-of-functions fashion
- Use React Router to create a single-page app that routes to different sub-component pages
Samples
These are some samples from the project.
Videos
Electron 1.0.0 demonstration video; highlights various functions of the program
Screenshots
WinForms

Adding exclusions to an archived item

Choosing options for archived items

Choosing a file name for saving the script file

Applying global options for all current and new archived items
Electron

The main window with information filled in

The error prompt displaying any field conflicts

The item editor with information filled in

The exclusion editor with information filled in
Code
WinForms
Before applying globals, this runs to make sure that existing items won't conflict with the new globals. The user is allowed to make items of the same file name, but only if their destinations differ. If the user applies a global destination to all the items, then any with the same file name will then have conflicts. This prevents the user from applying globals and notifies them of which items are conflicting.
private ErrorType ValidateGlobalDestination(List<ArchivedItem> items, out List<string> duplicates)
{
duplicates = new List<string>();
foreach (ArchivedItem itemA in items)
foreach (ArchivedItem itemB in items)
if (itemA != itemB)
if (itemA.fileName == itemB.fileName)
duplicates.Add("- Conflict between " + itemA.itemName + " and " + itemB.itemName);
if (duplicates.Count > 0)
return ErrorType.DUPLICATE;
else
return ErrorType.VALID;
}
In order to make sure that the user doesn't leave the item name blank or enter a duplicate exclusion, a validation method runs. A similar method runs when the ExitWithSave button is clicked on the ItemEditingWindow (the one that opens the ExclusionEditingWindow)
private void Button_ExitWithSave_Click(object sender, EventArgs e)
{
switch(ValidateEntry())
{
case ErrorType.DUPLICATE:
error = new ErrorPrompt(Errors.ERR_DUPL_EXCPTN);
error.ShowDialog();
break;
case ErrorType.MISSING:
error = new ErrorPrompt(Errors.ERR_MISSING_EXCPTN);
error.ShowDialog();
break;
case ErrorType.VALID:
default:
if (isAdding)
parent.CreateExclusion(Textbox_Name.Text, Checkbox_IsRecursive.Checked);
else
parent.UpdatedEditedExclusion(Textbox_Name.Text, Checkbox_IsRecursive.Checked);
Close();
break;
}
}
private ErrorType ValidateEntry()
{
if (Textbox_Name.Text == "")
return ErrorType.MISSING;
List<string> entries = parent.ExclusionRecursiveDefinitions.Keys.ToList();
foreach (string s in entries)
if (s == Textbox_Name.Text && isAdding)
return ErrorType.DUPLICATE;
return ErrorType.VALID;
}
Electron
Much of the code in writeScript is similar to the WinForms SaveFile method. Because fs.writeFile
is asynchronous, one major difference is that the function takes a callback (defaulting to a simple error throw) to handle errors. The code that is passed through here can be found in the handleWindowButtons
attaching a click handler to the Create Script button.
/*
** Creates a batch script based on incoming items to the provided destination using Node.js File System module
*/
function writeScript(items, destination, callback = function(err) { if (err) throw err; }) {
let out = MSG_BEGIN;
items.forEach(item => {
out += MSG_CURRENT_PREFIX + item.itemName + NEWLINE + CMD + SPACE;
if (item.type == ArchiveType.sevenz)
out += PARAM_TYPE + EXTENSION_SEVENZIP + SPACE;
else
out += PARAM_TYPE + EXTENSION_ZIP + SPACE;
if (item.compressionLevel > 0) {
switch (item.compressionMethod)
{
case CompressionMethod.bzip2:
out += TYPE_BZIP2 + SPACE;
case CompressionMethod.ppmd:
out += TYPE_PPMD + SPACE;
case CompressionMethod.lzma2:
out += TYPE_LZMA2 + SPACE;
case CompressionMethod.lzma:
default:
out += TYPE_LZMA + SPACE;
}
}
out += PARAM_LEVEL + item.compressionLevel.toString() + SPACE;
if (item.password != "")
out += PARAM_PASS + item.password + SPACE + PARAM_ENCRYPT + SPACE;
for (let i = 0; i < item.exclusions.length; i++) {
if (item.exclusionsRecursives[i])
out += PARAM_EXCLUDE_RECURSIVE + QUOTE + item.exclusions[i] + QUOTE + SPACE;
else
out += PARAM_EXCLUDE + QUOTE + item.exclusions[i] + QUOTE + SPACE;
}
out += QUOTE + item.destinationPath + "\\" + item.fileName;
if (item.type == ArchiveType.sevenz)
out += "." + EXTENSION_SEVENZIP + QUOTE + SPACE;
else
out += "." + EXTENSION_ZIP + QUOTE + SPACE;
out += QUOTE + item.sourcePath + ASTERISK + QUOTE + NEWLINE;
});
fs.writeFile(destination, out, callback);
}
In handleWindowButtons
, the click event is handled by first validating the main fields (playing an error if not valid). This checks to see if the script file destination isn't empty and is valid and that the list isn't empty. Then, it calls writeScript
, passing through an error handling callback that plays the error sound and opens an error window if there was an exception, otherwise playing the success sound if none was caught.
$("#Button_Archiving_CreateScript").click(function() {
if (validateMainFields()) {
writeScript(items, $("#Field_FileSettings_SavePath").val(), function(err) {
if (err) {
PageSounds.UI_Error.play();
$("#Window_Error").removeClass("error-window-hidden");
$("#Window_Error_Overlay").removeClass("error-window-hidden");
errWinDesc.html("Error writing file");
errWinAddl.html(err);
}
else
PageSounds.UI_Success.play();
});
}
else
PageSounds.UI_Error.play();
});
Places
Source Code (WinForms) | GitHub Repo |
Source Code (Electron JS) | GitHub Repo |
Source Code (Electron React) | GitHub Repo |