From e146bfd6c78deebcf1f04071cf6e666d25aa12fa Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Thu, 26 Oct 2023 15:52:32 -0700 Subject: [PATCH 01/11] Update data flow and fix forms --- src/renderer/src/stories/JSONSchemaForm.js | 68 ++++++++++--------- src/renderer/src/stories/JSONSchemaInput.js | 27 +++++--- src/renderer/src/stories/Main.js | 1 - src/renderer/src/stories/OptionalSection.js | 6 -- .../guided-mode/data/GuidedPathExpansion.js | 65 +++++++++++------- 5 files changed, 90 insertions(+), 77 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index c73e04526..c619adbf4 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -40,10 +40,6 @@ const componentCSS = ` line-height: 1.4285em; } - .invalid { - background: rgb(255, 229, 228) !important; - } - .guided--form-label { display: block; width: 100%; @@ -182,8 +178,8 @@ export class JSONSchemaForm extends LitElement { #updateRendered = (force) => force || this.#rendered === true ? (this.#rendered = new Promise( - (resolve) => (this.#toggleRendered = () => resolve((this.#rendered = true))) - )) + (resolve) => (this.#toggleRendered = () => resolve((this.#rendered = true))) + )) : this.#rendered; resolved = {}; // Keep track of actual resolved values—not just what the user provides as results @@ -320,11 +316,11 @@ export class JSONSchemaForm extends LitElement { validate = async (resolved) => { // Check if any required inputs are missing - const invalidInputs = await this.#validateRequirements(resolved); // get missing required paths - const isValid = this.requirementMode === "loose" ? true : !invalidInputs.length; + const requiredButNotSpecified = await this.#validateRequirements(resolved); // get missing required paths + const isValid = this.requirementMode === "loose" ? true : !requiredButNotSpecified.length; // Print out a detailed error message if any inputs are missing - let message = isValid ? "" : `${invalidInputs.length} required inputs are not specified properly.`; + let message = isValid ? "" : `${requiredButNotSpecified.length} required inputs are not specified properly.`; // Check if all inputs are valid const flaggedInputs = this.shadowRoot ? this.shadowRoot.querySelectorAll(".invalid") : []; @@ -376,11 +372,7 @@ export class JSONSchemaForm extends LitElement { #checkRequiredAfterChange = async (localPath) => { const path = [...localPath]; const name = path.pop(); - const element = this.shadowRoot - .querySelector(`#${localPath.join("-")}`) - .querySelector("jsonschema-input") - .getElement(); - const isValid = await this.triggerValidation(name, element, path, false); + const isValid = await this.triggerValidation(name, path, false); if (!isValid) return true; }; @@ -448,8 +440,8 @@ export class JSONSchemaForm extends LitElement {
${interactiveInput} @@ -498,9 +490,9 @@ export class JSONSchemaForm extends LitElement { }; // Checks missing required properties and throws an error if any are found - onInvalid = () => {}; - onLoaded = () => {}; - onUpdate = () => {}; + onInvalid = () => { }; + onLoaded = () => { }; + onUpdate = () => { }; #deleteExtraneousResults = (results, schema) => { for (let name in results) { @@ -551,10 +543,10 @@ export class JSONSchemaForm extends LitElement { return flattenRecursedValues(res); // Flatten on the last pass }; - validateOnChange = () => {}; - onStatusChange = () => {}; - onThrow = () => {}; - renderTable = () => {}; + validateOnChange = () => { }; + onStatusChange = () => { }; + onThrow = () => { }; + renderTable = () => { }; #getLink = (args) => { if (typeof args === "string") args = args.split("-"); @@ -603,7 +595,7 @@ export class JSONSchemaForm extends LitElement { }; // Assume this is going to return as a Promise—even if the change function isn't returning one - triggerValidation = async (name, element, path = [], checkLinks = true) => { + triggerValidation = async (name, path = [], checkLinks = true) => { const parent = this.#get(path, this.resolved); const pathToValidate = [...(this.base ?? []), ...path]; @@ -641,15 +633,22 @@ export class JSONSchemaForm extends LitElement { }, externalPath); } } - } else { + } + + else { + // For non-links, throw a basic requirement error if the property is required if (!errors.length && isRequired && !parent[name]) { - const schema = this.getSchema(localPath); - errors.push({ - message: `${schema.title ?? header(name)} is a required property.`, - type: "error", - missing: true, - }); // Throw at least a basic error if the property is required + + // Skip simple required checks in loose mode + if (this.requirementMode !== 'loose') { + const schema = this.getSchema(localPath); + errors.push({ + message: `${schema.title ?? header(name)} is a required property.`, + type: "error", + missing: true, + }); // Throw at least a basic error if the property is required + } } } @@ -676,8 +675,10 @@ export class JSONSchemaForm extends LitElement { warnings.forEach((info) => this.#addMessage(localPath, info, "warnings")); info.forEach((info) => this.#addMessage(localPath, info, "info")); + const input = this.getInput(localPath); + if (isValid && errors.length === 0) { - element.classList.remove("invalid"); + input.classList.remove("invalid"); const linkEl = this.#getLinkElement(externalPath); if (linkEl) linkEl.classList.remove("required", "conditional"); @@ -690,8 +691,9 @@ export class JSONSchemaForm extends LitElement { return true; } else { + // Add new invalid classes and errors - element.classList.add("invalid"); + input.classList.add("invalid"); const linkEl = this.#getLinkElement(externalPath); if (linkEl) linkEl.classList.add("required", "conditional"); diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 687d528e2..d089e84cf 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -30,6 +30,11 @@ export class JSONSchemaInput extends LitElement { display: block; } + :host(.invalid) .guided--input { + background: rgb(255, 229, 228) !important; + } + + .guided--input { width: 100%; border-radius: 4px; @@ -109,8 +114,8 @@ export class JSONSchemaInput extends LitElement { const { path: fullPath } = this; const path = typeof fullPath === "string" ? fullPath.split("-") : [...fullPath]; const name = path.splice(-1)[0]; - const el = this.getElement(); - this.#triggerValidation(name, el, path); + + this.#triggerValidation(name, path); this.#updateData(fullPath, value); if (el.type === "checkbox") el.checked = value; else el.value = value; @@ -125,8 +130,8 @@ export class JSONSchemaInput extends LitElement { this.value = value; // Update the latest value }; - #triggerValidation = (name, el, path) => - this.onValidate ? this.onValidate() : this.form ? this.form.triggerValidation(name, el, path) : ""; + #triggerValidation = (name, path) => + this.onValidate ? this.onValidate() : this.form ? this.form.triggerValidation(name, path) : ""; updated() { const el = this.getElement(); @@ -169,7 +174,7 @@ export class JSONSchemaInput extends LitElement { type: format, value: this.value, onSelect: (filePath) => this.#updateData(fullPath, filePath), - onChange: (filePath) => validateOnChange && this.#triggerValidation(name, el, path), + onChange: (filePath) => validateOnChange && this.#triggerValidation(name, path), onThrow: (...args) => this.#onThrow(...args), dialogOptions: this.form?.dialogOptions, dialogType: this.form?.dialogType, @@ -270,7 +275,7 @@ export class JSONSchemaInput extends LitElement { : [], onChange: async () => { this.#updateData(fullPath, list.items.length ? list.items.map((o) => o.value) : undefined); - if (validateOnChange) await this.#triggerValidation(name, list, path); + if (validateOnChange) await this.#triggerValidation(name, path); }, }); @@ -285,7 +290,7 @@ export class JSONSchemaInput extends LitElement { return html`
validateOnChange && this.#triggerValidation(name, list, path)} + @change=${() => validateOnChange && this.#triggerValidation(name, path)} > ${list} ${addButton}
@@ -298,7 +303,7 @@ export class JSONSchemaInput extends LitElement {