From 336f146b579bf7378e3f1531c043e93119697786 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 5 Sep 2023 13:05:41 -0700 Subject: [PATCH 01/60] Update file handling to support and block multiple file uploads --- .../src/stories/FileSystemSelector.js | 86 +++++++++++++++---- src/renderer/src/stories/JSONSchemaInput.js | 47 ++++++---- 2 files changed, 96 insertions(+), 37 deletions(-) diff --git a/src/renderer/src/stories/FileSystemSelector.js b/src/renderer/src/stories/FileSystemSelector.js index 06d397672..c154c5755 100644 --- a/src/renderer/src/stories/FileSystemSelector.js +++ b/src/renderer/src/stories/FileSystemSelector.js @@ -55,6 +55,8 @@ export class FilesystemSelector extends LitElement { super(); if (props.onSelect) this.onSelect = props.onSelect; if (props.onChange) this.onChange = props.onChange; + if (props.onThrow) this.onThrow = props.onThrow + this.multiple = props.multiple this.type = props.type ?? "file"; this.value = props.value ?? ""; this.dialogOptions = props.dialogOptions ?? {}; @@ -66,6 +68,11 @@ export class FilesystemSelector extends LitElement { onSelect = () => {}; onChange = () => {}; + #onThrow = (title, message) => { + message = message ? `
${title}
${message}` : title + if (this.onThrow) this.onThrow(message) + throw new Error(message); + } display = document.createElement("small"); @@ -76,41 +83,85 @@ export class FilesystemSelector extends LitElement { "noResolveAliases", ...(options.properties ?? []), ]; + + if (this.multiple && !options.properties.includes('multiSelections')) options.properties.push('multiSelections') + this.classList.add("active"); const result = await dialog[this.dialogType](options); + this.classList.remove("active"); - if (result.canceled) throw new Error("No file selected"); + if (result.canceled) this.#onCancel(); return result; }; - #handleFile = async (path) => { - if (!path) throw new Error("Unable to parse file path"); - this.value = path; + #onCancel = () => { + this.#onThrow(`No ${this.type} selected`, "The request was cancelled by the user"); + } + + #checkType = (value) => { + const isLikelyFile = value.split('.').length !== 1 + if ((this.type === 'directory' && isLikelyFile) || (this.type === 'file' && !isLikelyFile)) this.#onThrow("Incorrect filesystem object", `Please provide a ${this.type} instead.`) + } + + #handleFiles = async (pathOrPaths) => { + if (!pathOrPaths) this.#onThrow("No paths detected", `Unable to parse ${this.type} path${this.multiple ? 's' : ''}`); + + if (Array.isArray(pathOrPaths)) pathOrPaths.forEach(this.#checkType) + else this.#checkType(pathOrPaths) + + let resolvedValue = pathOrPaths + if (Array.isArray(resolvedValue) && !this.multiple) { + if (resolvedValue.length > 1) this.#onThrow(`Too many ${this.type === 'directory' ? 'directories' : 'files'} detected`, `This selector will only accept one.`); + resolvedValue = resolvedValue[0] + } + + + if (this.multiple && !Array.isArray(resolvedValue)) resolvedValue = [] + + this.value = resolvedValue; this.onSelect(this.value); const event = new Event("change"); // Create a new change event this.dispatchEvent(event); }; render() { - const preprocessed = this.value.replaceAll("\\", "/"); - if (preprocessed !== this.value) { - this.value = preprocessed; - this.#handleFile(this.value); // Notify of the change to the separators + + let resolved, isUpdated; + + const isArray = Array.isArray(this.value) + const len = isArray ? this.value.length : 0 + + if (isArray) { + resolved = this.value.map(str => str.replaceAll("\\", "/")); + isUpdated = JSON.stringify(resolved) !== JSON.stringify(this.value) + } else { + resolved = this.value.replaceAll("\\", "/"); + isUpdated = resolved !== this.value + } + + if (isUpdated) { + this.value = resolved; + this.#handleFiles(this.value); // Notify of the change to the separators + return } return html` `; } diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 84b4fcf08..78894c9a2 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -8,7 +8,11 @@ import { Button } from "./Button"; import { List } from "./List"; import { Modal } from "./Modal"; -const filesystemQueries = ["file", "directory"]; +const isFilesystemSelector = (format) => { + const matched = name.match(/(.+_)?(.+)_paths?/); + if (!format && matched) format = matched[2] === "folder" ? "directory" : matched[2]; + return ["file", "directory"].includes(format) ? format : null; // Handle file and directory formats +} export class JSONSchemaInput extends LitElement { static get styles() { @@ -127,6 +131,22 @@ export class JSONSchemaInput extends LitElement { const hasItemsRef = "items" in info && "$ref" in info.items; if (!("items" in info) || (!("type" in info.items) && !hasItemsRef)) info.items = { type: "string" }; + + // Handle file and directory formats + const createFilesystemSelector = (format) => { + const el = new FilesystemSelector({ + type: format, + value: this.value, + onSelect: (filePath) => this.#updateData(fullPath, filePath), + onChange: (filePath) => validateOnChange && this.#triggerValidation(name, el, path), + onThrow: (...args) => this.form.onThrow(...args), + dialogOptions: this.form.dialogOptions, + dialogType: this.form.dialogType, + multiple: isArray + }); + el.classList.add("schema-input"); + return el; + } if (isArray) { // if ('value' in this && !Array.isArray(this.value)) this.value = [ this.value ] @@ -134,7 +154,10 @@ export class JSONSchemaInput extends LitElement { // Catch tables const itemSchema = this.form.getSchema("items", info); const isTable = itemSchema.type === "object"; - if (isTable) { + + const fileSystemFormat = isFilesystemSelector(itemSchema.format) + if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat) + else if (isTable) { const tableMetadata = { schema: itemSchema, data: this.value, @@ -242,23 +265,9 @@ export class JSONSchemaInput extends LitElement { @change=${(ev) => validateOnChange && this.#triggerValidation(name, ev.target, path)} />`; } else if (info.type === "string" || info.type === "number") { - let format = info.format; - const matched = name.match(/(.+_)?(.+)_path/); - if (!format && matched) format = matched[2] === "folder" ? "directory" : matched[2]; - - // Handle file and directory formats - if (filesystemQueries.includes(format)) { - const el = new FilesystemSelector({ - type: format, - value: this.value, - onSelect: (filePath) => this.#updateData(fullPath, filePath), - onChange: (filePath) => validateOnChange && this.#triggerValidation(name, el, path), - dialogOptions: this.form.dialogOptions, - dialogType: this.form.dialogType, - }); - el.classList.add("schema-input"); - return el; - } + + const fileSystemFormat = isFilesystemSelector(info.format) + if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat) // Handle long string formats else if (info.format === "long" || isArray) From 410d6f906641944ffb7ab5cf1c273476f56377d7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 20:09:16 +0000 Subject: [PATCH 02/60] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../src/stories/FileSystemSelector.js | 83 +++++++++++-------- src/renderer/src/stories/JSONSchemaInput.js | 18 ++-- 2 files changed, 55 insertions(+), 46 deletions(-) diff --git a/src/renderer/src/stories/FileSystemSelector.js b/src/renderer/src/stories/FileSystemSelector.js index c154c5755..fe3262220 100644 --- a/src/renderer/src/stories/FileSystemSelector.js +++ b/src/renderer/src/stories/FileSystemSelector.js @@ -55,8 +55,8 @@ export class FilesystemSelector extends LitElement { super(); if (props.onSelect) this.onSelect = props.onSelect; if (props.onChange) this.onChange = props.onChange; - if (props.onThrow) this.onThrow = props.onThrow - this.multiple = props.multiple + if (props.onThrow) this.onThrow = props.onThrow; + this.multiple = props.multiple; this.type = props.type ?? "file"; this.value = props.value ?? ""; this.dialogOptions = props.dialogOptions ?? {}; @@ -69,10 +69,10 @@ export class FilesystemSelector extends LitElement { onSelect = () => {}; onChange = () => {}; #onThrow = (title, message) => { - message = message ? `
${title}
${message}` : title - if (this.onThrow) this.onThrow(message) + message = message ? `
${title}
${message}` : title; + if (this.onThrow) this.onThrow(message); throw new Error(message); - } + }; display = document.createElement("small"); @@ -84,8 +84,9 @@ export class FilesystemSelector extends LitElement { ...(options.properties ?? []), ]; - if (this.multiple && !options.properties.includes('multiSelections')) options.properties.push('multiSelections') - + if (this.multiple && !options.properties.includes("multiSelections")) + options.properties.push("multiSelections"); + this.classList.add("active"); const result = await dialog[this.dialogType](options); @@ -96,27 +97,32 @@ export class FilesystemSelector extends LitElement { #onCancel = () => { this.#onThrow(`No ${this.type} selected`, "The request was cancelled by the user"); - } + }; #checkType = (value) => { - const isLikelyFile = value.split('.').length !== 1 - if ((this.type === 'directory' && isLikelyFile) || (this.type === 'file' && !isLikelyFile)) this.#onThrow("Incorrect filesystem object", `Please provide a ${this.type} instead.`) - } + const isLikelyFile = value.split(".").length !== 1; + if ((this.type === "directory" && isLikelyFile) || (this.type === "file" && !isLikelyFile)) + this.#onThrow("Incorrect filesystem object", `Please provide a ${this.type} instead.`); + }; #handleFiles = async (pathOrPaths) => { - if (!pathOrPaths) this.#onThrow("No paths detected", `Unable to parse ${this.type} path${this.multiple ? 's' : ''}`); + if (!pathOrPaths) + this.#onThrow("No paths detected", `Unable to parse ${this.type} path${this.multiple ? "s" : ""}`); - if (Array.isArray(pathOrPaths)) pathOrPaths.forEach(this.#checkType) - else this.#checkType(pathOrPaths) + if (Array.isArray(pathOrPaths)) pathOrPaths.forEach(this.#checkType); + else this.#checkType(pathOrPaths); - let resolvedValue = pathOrPaths + let resolvedValue = pathOrPaths; if (Array.isArray(resolvedValue) && !this.multiple) { - if (resolvedValue.length > 1) this.#onThrow(`Too many ${this.type === 'directory' ? 'directories' : 'files'} detected`, `This selector will only accept one.`); - resolvedValue = resolvedValue[0] + if (resolvedValue.length > 1) + this.#onThrow( + `Too many ${this.type === "directory" ? "directories" : "files"} detected`, + `This selector will only accept one.` + ); + resolvedValue = resolvedValue[0]; } - - if (this.multiple && !Array.isArray(resolvedValue)) resolvedValue = [] + if (this.multiple && !Array.isArray(resolvedValue)) resolvedValue = []; this.value = resolvedValue; this.onSelect(this.value); @@ -125,42 +131,39 @@ export class FilesystemSelector extends LitElement { }; render() { - let resolved, isUpdated; - const isArray = Array.isArray(this.value) - const len = isArray ? this.value.length : 0 + const isArray = Array.isArray(this.value); + const len = isArray ? this.value.length : 0; if (isArray) { - resolved = this.value.map(str => str.replaceAll("\\", "/")); - isUpdated = JSON.stringify(resolved) !== JSON.stringify(this.value) + resolved = this.value.map((str) => str.replaceAll("\\", "/")); + isUpdated = JSON.stringify(resolved) !== JSON.stringify(this.value); } else { resolved = this.value.replaceAll("\\", "/"); - isUpdated = resolved !== this.value + isUpdated = resolved !== this.value; } if (isUpdated) { this.value = resolved; this.#handleFiles(this.value); // Notify of the change to the separators - return + return; } return html` `; } diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 78894c9a2..f87a96b94 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -12,7 +12,7 @@ const isFilesystemSelector = (format) => { const matched = name.match(/(.+_)?(.+)_paths?/); if (!format && matched) format = matched[2] === "folder" ? "directory" : matched[2]; return ["file", "directory"].includes(format) ? format : null; // Handle file and directory formats -} +}; export class JSONSchemaInput extends LitElement { static get styles() { @@ -131,7 +131,7 @@ export class JSONSchemaInput extends LitElement { const hasItemsRef = "items" in info && "$ref" in info.items; if (!("items" in info) || (!("type" in info.items) && !hasItemsRef)) info.items = { type: "string" }; - + // Handle file and directory formats const createFilesystemSelector = (format) => { const el = new FilesystemSelector({ @@ -142,11 +142,11 @@ export class JSONSchemaInput extends LitElement { onThrow: (...args) => this.form.onThrow(...args), dialogOptions: this.form.dialogOptions, dialogType: this.form.dialogType, - multiple: isArray + multiple: isArray, }); el.classList.add("schema-input"); return el; - } + }; if (isArray) { // if ('value' in this && !Array.isArray(this.value)) this.value = [ this.value ] @@ -155,8 +155,8 @@ export class JSONSchemaInput extends LitElement { const itemSchema = this.form.getSchema("items", info); const isTable = itemSchema.type === "object"; - const fileSystemFormat = isFilesystemSelector(itemSchema.format) - if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat) + const fileSystemFormat = isFilesystemSelector(itemSchema.format); + if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat); else if (isTable) { const tableMetadata = { schema: itemSchema, @@ -265,10 +265,8 @@ export class JSONSchemaInput extends LitElement { @change=${(ev) => validateOnChange && this.#triggerValidation(name, ev.target, path)} />`; } else if (info.type === "string" || info.type === "number") { - - const fileSystemFormat = isFilesystemSelector(info.format) - if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat) - + const fileSystemFormat = isFilesystemSelector(info.format); + if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat); // Handle long string formats else if (info.format === "long" || isArray) return html`