From bae0c205515f5ef2e2bfb48ede72ad4ecc7b8f80 Mon Sep 17 00:00:00 2001 From: James Oswald Date: Sun, 5 May 2024 12:14:47 -0400 Subject: [PATCH 1/7] Use FileSaver.js for slow download on firefox --- package-lock.json | 13 +++++++++++ package.json | 2 ++ src/SharedToolUtils/DrawUtils.ts | 4 ++-- src/index.ts | 37 +++++++++++++------------------- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90c9c725..87fc91dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "file-saver": "^2.0.5", "fork-awesome": "^1.2.0", "theme-change": "^2.5.0" }, "devDependencies": { + "@types/file-saver": "^2.0.7", "@types/node": "20.12.7", "@types/wicg-file-system-access": "^2023.10.5", "gts": "^5.3.0", @@ -868,6 +870,12 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2122,6 +2130,11 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", diff --git a/package.json b/package.json index 0ecca541..001852b1 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ }, "homepage": "https://github.com/RAIRLab/PeirceMyHeart#readme", "devDependencies": { + "@types/file-saver": "^2.0.7", "@types/node": "20.12.7", "@types/wicg-file-system-access": "^2023.10.5", "gts": "^5.3.0", @@ -43,6 +44,7 @@ "vitest": "^1.5.2" }, "dependencies": { + "file-saver": "^2.0.5", "fork-awesome": "^1.2.0", "theme-change": "^2.5.0" } diff --git a/src/SharedToolUtils/DrawUtils.ts b/src/SharedToolUtils/DrawUtils.ts index 1fbecdcb..a27fed30 100644 --- a/src/SharedToolUtils/DrawUtils.ts +++ b/src/SharedToolUtils/DrawUtils.ts @@ -5,7 +5,7 @@ * @author Anusha Tiwari */ -import {aegStringify} from "../index"; +import {aegJsonString} from "../index"; import {AEGTree} from "../AEG/AEGTree"; import {AtomNode} from "../AEG/AtomNode"; import {CutNode} from "../AEG/CutNode"; @@ -174,7 +174,7 @@ export function redrawTree(tree: AEGTree, color?: string): void { cutDisplay.innerHTML = tree.toString(); cleanCanvas(); redrawCut(tree.sheet, color); - window.treeString = aegStringify(tree); + window.treeString = aegJsonString(tree); } /** diff --git a/src/index.ts b/src/index.ts index 106e6169..69bce561 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,9 @@ import * as ErasureTool from "./ProofTools/ErasureTool"; import * as ProofResizeTool from "./ProofTools/ProofResizeTool"; import * as PasteInProof from "./ProofTools/PasteInProof"; +//Cross-browser file saving library. +import FileSaver from "file-saver"; + //Setting up canvas... const canvas: HTMLCanvasElement = document.getElementById("canvas"); canvas.width = window.innerWidth; @@ -75,11 +78,11 @@ let hasMouseIn = true; //Global window exports. //TODO: move these under the global import window.tree = TreeContext.tree; -window.treeString = aegStringify(window.tree); +window.treeString = aegJsonString(window.tree); window.atomTool = Tool.atomTool; window.cutTool = Tool.cutTool; window.dragTool = Tool.dragTool; -window.aegStringify = aegStringify; +window.aegJsonString = aegJsonString; window.saveMode = saveMode; window.loadMode = loadMode; window.drawMoveSingleTool = Tool.drawMoveSingleTool; @@ -115,7 +118,7 @@ declare global { dragTool: Tool; saveMode: () => void; loadMode: () => void; - aegStringify: (treeData: AEGTree | ProofModeNode[]) => string; + aegJsonString: (treeData: AEGTree | ProofModeNode[]) => string; drawMoveSingleTool: Tool; drawMoveMultiTool: Tool; copySingleTool: Tool; @@ -196,12 +199,13 @@ function setTool(state: Tool): void { } } /** - * Creates and returns the stringification of the incoming data. Uses tab characters as delimiters. - * - * @param treeData Incoming data. - * @returns Stringification of treeData. + * Creates and returns the json string of the given AEG Tree object. + * Uses tab characters as delimiters. + * + * @param treeData An AEG Tree object. + * @returns json string of treeData. */ -export function aegStringify(treeData: AEGTree | ProofModeNode[]): string { +export function aegJsonString(treeData: AEGTree | ProofModeNode[]): string { return JSON.stringify(treeData, null, "\t"); } @@ -213,7 +217,7 @@ async function saveMode(): Promise { let data: AEGTree | ProofModeNode[]; if (TreeContext.modeState === "Draw") { - name = "AEG Tree"; + name = TreeContext.tree.toString() data = TreeContext.tree; } else { if (TreeContext.proof.length === 1) { @@ -235,22 +239,11 @@ async function saveMode(): Promise { excludeAcceptAllOption: true, suggestedName: name, startIn: "downloads", - types: [ - { - description: "JSON Files", - accept: { - "text/json": [".json"], - }, - }, - ], + types: [{accept: {"text/json": [".json"]}}] }); saveFile(saveHandle, data); } else { - //Quick Download... - const f = document.createElement("a"); - f.href = aegStringify(data); - f.download = name + ".json"; - f.click(); + FileSaver(aegJsonString(data), name + ".json"); } } catch (error) { //Catch error but do nothing. Discussed in Issue #247. From dd38a316f733488037e45da6c2b6405693e41818 Mon Sep 17 00:00:00 2001 From: James Oswald Date: Sun, 5 May 2024 12:48:07 -0400 Subject: [PATCH 2/7] Fix saving and loading --- src/AEG-IO.ts | 123 +++++++++++++++++++++++++++++++ src/SharedToolUtils/DrawUtils.ts | 2 +- src/index.ts | 111 +--------------------------- 3 files changed, 125 insertions(+), 111 deletions(-) diff --git a/src/AEG-IO.ts b/src/AEG-IO.ts index bca608e6..cf573f2b 100644 --- a/src/AEG-IO.ts +++ b/src/AEG-IO.ts @@ -4,6 +4,10 @@ * @author Anusha Tiwari */ +import {TreeContext} from "./TreeContext"; +import {redrawProof, redrawTree} from "./SharedToolUtils/DrawUtils"; +import {appendStep} from "./ProofHistory/ProofHistory"; + import {AEGTree} from "./AEG/AEGTree"; import {AtomNode} from "./AEG/AtomNode"; import {CutNode} from "./AEG/CutNode"; @@ -11,6 +15,9 @@ import {Ellipse} from "./AEG/Ellipse"; import {Point} from "./AEG/Point"; import {ProofModeMove, ProofModeNode} from "./ProofHistory/ProofModeNode"; +//Cross-browser file saving library. +import FileSaver from "file-saver"; + /** * Describes The Sheet of Assertion in JSON files. */ @@ -158,3 +165,119 @@ function toAtom(atomData: atomObj): AtomNode { return new AtomNode(identifier, origin, atomData.internalWidth, atomData.internalHeight); } + +/** + * Creates and returns the json string of the given AEG Tree object. + * Uses tab characters as delimiters. + * + * @param treeData An AEG Tree object. + * @returns json string of treeData. + */ +export function aegJsonString(treeData: AEGTree | ProofModeNode[]): string { + return JSON.stringify(treeData, null, "\t"); +} + +/** + * Calls appropriate methods to save the current AEGTree as a file. + */ +export async function saveMode(): Promise { + let name: string; + let data: AEGTree | ProofModeNode[]; + + if (TreeContext.modeState === "Draw") { + name = TreeContext.tree.toString() + data = TreeContext.tree; + } else { + if (TreeContext.proof.length === 1) { + name = "One-Step Proof"; + } else { + name = + TreeContext.proof[0].tree.toString() + + " PROVES " + + TreeContext.getLastProofStep().tree.toString(); + } + data = TreeContext.proof; + } + + //Errors caused by file handler or HTML download element should not be displayed. + try { + //Dialog based download + if ("showSaveFilePicker" in window) { + const saveHandle = await window.showSaveFilePicker({ + excludeAcceptAllOption: true, + suggestedName: name, + startIn: "downloads", + types: [{accept: {"text/json": [".json"]}}] + }); + saveFile(saveHandle, data); + } else { + //Fallback to immediate download if showSaveFilePicker is not supported. + let blob = new Blob([aegJsonString(data)], {type: "text/json"}); + FileSaver(blob, name + ".json"); + } + } catch (error) { + //Catch error but do nothing. Discussed in Issue #247. + } +} + +function readFile(file: File) { + const reader = new FileReader(); + reader.addEventListener("load", () => { + const aegData = reader.result; + if (typeof aegData === "string") { + const loadData = loadFile(TreeContext.modeState, aegData); + if (TreeContext.modeState === "Draw") { + //Loads data. + TreeContext.tree = loadData as AEGTree; + //Redraws tree which is now the parsed loadData. + redrawTree(TreeContext.tree); + } else if (TreeContext.modeState === "Proof") { + //Clears current proof. + TreeContext.clearProof(); + //Loads data for the new proof. + TreeContext.proof = loadData as ProofModeNode[]; + //Removes default start step. + document.getElementById("Row: 1")?.remove(); + //Adds button for each step of the loaded proof to the history bar. + for (let i = 0; i < TreeContext.proof.length; i++) { + appendStep(TreeContext.proof[i], i + 1); + } + TreeContext.currentProofStep = TreeContext.proof[TreeContext.proof.length - 1]; + redrawProof(); + } + } else { + console.log("Loading failed because reading the file was unsuccessful."); + } + }); + reader.readAsText(file); +} + +/** + * Calls the appropriate methods to load files and convert them to equivalent AEGTrees. + */ +export async function loadMode(): Promise { + + try { + if("showOpenFilePicker" in window) { + const [fileHandle] = await window.showOpenFilePicker({ + excludeAcceptAllOption: true, + multiple: false, + startIn: "downloads", + types: [{accept: {"text/json": [".json"]}}] + }); + const file = await fileHandle.getFile(); + readFile(file); + } else { + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = ".json"; + fileInput.addEventListener("change", () => { + const file = fileInput.files?.item(0); + readFile(file!); + }); + fileInput.click(); + } + } catch (error) { + //Do nothing. + } +} diff --git a/src/SharedToolUtils/DrawUtils.ts b/src/SharedToolUtils/DrawUtils.ts index a27fed30..dc100a2a 100644 --- a/src/SharedToolUtils/DrawUtils.ts +++ b/src/SharedToolUtils/DrawUtils.ts @@ -5,7 +5,7 @@ * @author Anusha Tiwari */ -import {aegJsonString} from "../index"; +import {aegJsonString} from "../AEG-IO"; import {AEGTree} from "../AEG/AEGTree"; import {AtomNode} from "../AEG/AtomNode"; import {CutNode} from "../AEG/CutNode"; diff --git a/src/index.ts b/src/index.ts index 69bce561..e4a5c3a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,10 +8,8 @@ */ import {AEGTree} from "./AEG/AEGTree"; -import {appendStep} from "./ProofHistory/ProofHistory"; -import {loadFile, saveFile} from "./AEG-IO"; +import {loadMode, saveMode, aegJsonString} from "./AEG-IO"; import {ProofModeNode} from "./ProofHistory/ProofModeNode"; -import {redrawProof, redrawTree} from "./SharedToolUtils/DrawUtils"; import {toggleHandler} from "./ToggleModes"; import {Tool, TreeContext} from "./TreeContext"; @@ -41,9 +39,6 @@ import * as ErasureTool from "./ProofTools/ErasureTool"; import * as ProofResizeTool from "./ProofTools/ProofResizeTool"; import * as PasteInProof from "./ProofTools/PasteInProof"; -//Cross-browser file saving library. -import FileSaver from "file-saver"; - //Setting up canvas... const canvas: HTMLCanvasElement = document.getElementById("canvas"); canvas.width = window.innerWidth; @@ -198,111 +193,7 @@ function setTool(state: Tool): void { break; } } -/** - * Creates and returns the json string of the given AEG Tree object. - * Uses tab characters as delimiters. - * - * @param treeData An AEG Tree object. - * @returns json string of treeData. - */ -export function aegJsonString(treeData: AEGTree | ProofModeNode[]): string { - return JSON.stringify(treeData, null, "\t"); -} - -/** - * Calls appropriate methods to save the current AEGTree as a file. - */ -async function saveMode(): Promise { - let name: string; - let data: AEGTree | ProofModeNode[]; - - if (TreeContext.modeState === "Draw") { - name = TreeContext.tree.toString() - data = TreeContext.tree; - } else { - if (TreeContext.proof.length === 1) { - name = "One-Step Proof"; - } else { - name = - TreeContext.proof[0].tree.toString() + - " PROVES " + - TreeContext.getLastProofStep().tree.toString(); - } - data = TreeContext.proof; - } - - //Errors caused by file handler or HTML download element should not be displayed. - try { - //Slow Download... - if ("showSaveFilePicker" in window) { - const saveHandle = await window.showSaveFilePicker({ - excludeAcceptAllOption: true, - suggestedName: name, - startIn: "downloads", - types: [{accept: {"text/json": [".json"]}}] - }); - saveFile(saveHandle, data); - } else { - FileSaver(aegJsonString(data), name + ".json"); - } - } catch (error) { - //Catch error but do nothing. Discussed in Issue #247. - } -} - -/** - * Calls the appropriate methods to load files and convert them to equivalent AEGTrees. - */ -async function loadMode(): Promise { - try { - const [fileHandle] = await window.showOpenFilePicker({ - excludeAcceptAllOption: true, - multiple: false, - startIn: "downloads", - types: [ - { - description: "JSON Files", - accept: { - "text/json": [".json"], - }, - }, - ], - }); - const file = await fileHandle.getFile(); - const reader = new FileReader(); - reader.addEventListener("load", () => { - const aegData = reader.result; - if (typeof aegData === "string") { - const loadData = loadFile(TreeContext.modeState, aegData); - if (TreeContext.modeState === "Draw") { - //Loads data. - TreeContext.tree = loadData as AEGTree; - //Redraws tree which is now the parsed loadData. - redrawTree(TreeContext.tree); - } else if (TreeContext.modeState === "Proof") { - //Clears current proof. - TreeContext.clearProof(); - //Loads data for the new proof. - TreeContext.proof = loadData as ProofModeNode[]; - //Removes default start step. - document.getElementById("Row: 1")?.remove(); - //Adds button for each step of the loaded proof to the history bar. - for (let i = 0; i < TreeContext.proof.length; i++) { - appendStep(TreeContext.proof[i], i + 1); - } - TreeContext.currentProofStep = TreeContext.proof[TreeContext.proof.length - 1]; - redrawProof(); - } - } else { - console.log("Loading failed because reading the file was unsuccessful."); - } - }); - reader.readAsText(file); - } catch (error) { - //Do nothing. - } -} /** * Handles CTRL+Z undo operations according to whether the program is in Draw or Proof Mode. From 27b50e22e4a6a6b4b2c582b4fe2e084416033894 Mon Sep 17 00:00:00 2001 From: James Oswald Date: Sun, 5 May 2024 12:53:10 -0400 Subject: [PATCH 3/7] Lint fixes --- src/AEG-IO.ts | 13 ++++++------- src/index.ts | 1 - 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/AEG-IO.ts b/src/AEG-IO.ts index cf573f2b..aa605fb2 100644 --- a/src/AEG-IO.ts +++ b/src/AEG-IO.ts @@ -169,7 +169,7 @@ function toAtom(atomData: atomObj): AtomNode { /** * Creates and returns the json string of the given AEG Tree object. * Uses tab characters as delimiters. - * + * * @param treeData An AEG Tree object. * @returns json string of treeData. */ @@ -185,7 +185,7 @@ export async function saveMode(): Promise { let data: AEGTree | ProofModeNode[]; if (TreeContext.modeState === "Draw") { - name = TreeContext.tree.toString() + name = TreeContext.tree.toString(); data = TreeContext.tree; } else { if (TreeContext.proof.length === 1) { @@ -207,12 +207,12 @@ export async function saveMode(): Promise { excludeAcceptAllOption: true, suggestedName: name, startIn: "downloads", - types: [{accept: {"text/json": [".json"]}}] + types: [{accept: {"text/json": [".json"]}}], }); saveFile(saveHandle, data); } else { //Fallback to immediate download if showSaveFilePicker is not supported. - let blob = new Blob([aegJsonString(data)], {type: "text/json"}); + const blob = new Blob([aegJsonString(data)], {type: "text/json"}); FileSaver(blob, name + ".json"); } } catch (error) { @@ -256,14 +256,13 @@ function readFile(file: File) { * Calls the appropriate methods to load files and convert them to equivalent AEGTrees. */ export async function loadMode(): Promise { - try { - if("showOpenFilePicker" in window) { + if ("showOpenFilePicker" in window) { const [fileHandle] = await window.showOpenFilePicker({ excludeAcceptAllOption: true, multiple: false, startIn: "downloads", - types: [{accept: {"text/json": [".json"]}}] + types: [{accept: {"text/json": [".json"]}}], }); const file = await fileHandle.getFile(); readFile(file); diff --git a/src/index.ts b/src/index.ts index e4a5c3a0..6e92d3a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -194,7 +194,6 @@ function setTool(state: Tool): void { } } - /** * Handles CTRL+Z undo operations according to whether the program is in Draw or Proof Mode. */ From fcc076bde32507a1f6bb1498a57066591045c53d Mon Sep 17 00:00:00 2001 From: James Oswald Date: Mon, 6 May 2024 11:29:50 -0400 Subject: [PATCH 4/7] Update package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 039a6acb..f8fc6251 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "homepage": "https://github.com/RAIRLab/PeirceMyHeart#readme", "devDependencies": { "@types/file-saver": "^2.0.7", - "@types/node": "20.12.7", "@types/node": "20.12.8", "@types/wicg-file-system-access": "^2023.10.5", "gts": "^5.3.0", From 851945af7ff5aac9161711416b752aec4bde2b16 Mon Sep 17 00:00:00 2001 From: James Oswald Date: Mon, 6 May 2024 11:29:55 -0400 Subject: [PATCH 5/7] Update package-lock.json --- package-lock.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index a2319ca9..412a7698 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ }, "devDependencies": { "@types/file-saver": "^2.0.7", - "@types/node": "20.12.7", "@types/node": "20.12.8", "@types/wicg-file-system-access": "^2023.10.5", "gts": "^5.3.0", From d2b0aa468d6dc8d357b80b7a4a67203b95caeeaa Mon Sep 17 00:00:00 2001 From: Ryan Reilly Date: Mon, 6 May 2024 11:54:26 -0400 Subject: [PATCH 6/7] Firefox debug button and typo fixes --- .vscode/launch.json | 19 +++++++++++++------ src/AEG-IO.ts | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 9cfcde95..f94732cc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,20 +4,27 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Chrome Vite Debug", + "url": "http://localhost:5173/", + "webRoot": "${workspaceRoot}/src", + //"sourceMaps": true, + }, { "type": "msedge", - "name": "Edge Vite Debug", "request": "launch", + "name": "Edge Vite Debug", "url": "http://localhost:5173/", "webRoot": "${workspaceFolder}/src" }, { - "type": "chrome", + "type": "firefox", "request": "launch", - "name": "Chrome Vite Debug", + "name": "Firefox Vite Debug", "url": "http://localhost:5173/", - "webRoot": "${workspaceRoot}/src", - //"sourceMaps": true, - } + "webRoot": "${workspaceFolder}/src" + }, ] } \ No newline at end of file diff --git a/src/AEG-IO.ts b/src/AEG-IO.ts index aa605fb2..e79e9322 100644 --- a/src/AEG-IO.ts +++ b/src/AEG-IO.ts @@ -153,7 +153,7 @@ function toCut(cutData: cutObj): CutNode { } /** - * Parses the incoming AtomObject and returns and equivalent AtomNode. + * Parses the incoming AtomObject and returns an equivalent AtomNode. * * @param atomData Incoming AtomObject. * @returns AtomNode equivalent of atomData. @@ -168,7 +168,7 @@ function toAtom(atomData: atomObj): AtomNode { /** * Creates and returns the json string of the given AEG Tree object. - * Uses tab characters as delimiters. + * Uses tab characters as delimiters. * * @param treeData An AEG Tree object. * @returns json string of treeData. From 5c7f07bcf68fcff5d282b4a0e57e4e83290b7f65 Mon Sep 17 00:00:00 2001 From: Ryan Reilly Date: Mon, 6 May 2024 12:28:38 -0400 Subject: [PATCH 7/7] Added firefox debugger to extension suggestions --- .vscode/extensions.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index a1b63682..a0eb3feb 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,7 @@ { "recommendations": [ "dbaeumer.vscode-eslint", + "firefox-devtools.vscode-firefox-debug", "streetsidesoftware.code-spell-checker" ] } \ No newline at end of file