From 14bcabbcac89dcf025c5f37ac5d9f99caaa015fe Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 1 Nov 2023 12:56:25 -0700 Subject: [PATCH 01/21] Basic visual disabling --- src/renderer/src/stories/Accordion.js | 108 +++++++++++--------- src/renderer/src/stories/InstanceManager.js | 13 +-- src/renderer/src/stories/JSONSchemaForm.js | 42 +++++--- 3 files changed, 94 insertions(+), 69 deletions(-) diff --git a/src/renderer/src/stories/Accordion.js b/src/renderer/src/stories/Accordion.js index cae120639..49deee89d 100644 --- a/src/renderer/src/stories/Accordion.js +++ b/src/renderer/src/stories/Accordion.js @@ -23,6 +23,10 @@ export class Accordion extends LitElement { box-sizing: border-box; } + :host { + display: block; + } + .header { display: flex; align-items: end; @@ -123,7 +127,7 @@ export class Accordion extends LitElement { content: "${successSymbol}"; } - .guided--nav-bar-dropdown:hover { + .guided--nav-bar-dropdown.content:hover { cursor: pointer; background-color: lightgray; } @@ -143,34 +147,48 @@ export class Accordion extends LitElement { padding-left: 0px; overflow-y: auto; } + + .disabled { + opacity: 0.5; + pointer-events: none; + } `; } + // declare info: { + // open: boolean; + // status: "error" | "warning" | "valid"; + // disabled: boolean + // } + static get properties() { return { - sections: { type: Object, reflect: false }, + name: { type: String, reflect: true }, + info : { type: Object, reflect: true }, }; } - constructor({ sections = {}, contentPadding } = {}) { + constructor({ name, subtitle, content, info = {}, contentPadding } = {}) { super(); - this.sections = sections; + this.name = name; + this.subtitle = subtitle; + this.content = content; + this.info = info; this.contentPadding = contentPadding; } updated() { - Object.entries(this.sections).map(([sectionName, info]) => { - const isActive = info.open; - if (isActive) this.#toggleDropdown(sectionName, true); - else this.#toggleDropdown(sectionName, false); - }); + if (!this.content) return + const isActive = this.info.open; + if (isActive) this.toggle(true); + else this.toggle(false); } - setSectionStatus = (sectionName, status) => { - const el = this.shadowRoot.querySelector("[data-section-name='" + sectionName + "']"); + setStatus = (status) => { + const el = this.shadowRoot.getElementById('dropdown') el.classList.remove("error", "warning", "valid"); el.classList.add(status); - this.sections[sectionName].status = status; + this.info.status = status; }; onClick = () => {}; // Set by the user @@ -183,60 +201,52 @@ export class Accordion extends LitElement { } }; - #toggleDropdown = (sectionName, forcedState) => { + toggle = (forcedState) => { const hasForce = forcedState !== undefined; - const toggledState = !this.sections[sectionName].open; + const toggledState = !this.info.open; let state = hasForce ? forcedState : toggledState; //remove hidden from child elements with guided--nav-bar-section-page class - const section = this.shadowRoot.querySelector("[data-section='" + sectionName + "']"); + const section = this.shadowRoot.getElementById('section') section.toggleAttribute("hidden", hasForce ? !state : undefined); - const dropdown = this.shadowRoot.querySelector("[data-section-name='" + sectionName + "']"); + const dropdown = this.shadowRoot.getElementById('dropdown') this.#updateClass("active", dropdown, !state); //toggle the chevron const chevron = dropdown.querySelector("nwb-chevron"); chevron.direction = state ? "bottom" : "right"; - this.sections[sectionName].open = state; + this.info.open = state; }; render() { return html` - +
+ + ${this.content ? html`` : ''} +
`; } } diff --git a/src/renderer/src/stories/InstanceManager.js b/src/renderer/src/stories/InstanceManager.js index 3336dec28..dad1b7d31 100644 --- a/src/renderer/src/stories/InstanceManager.js +++ b/src/renderer/src/stories/InstanceManager.js @@ -124,6 +124,10 @@ export class InstanceManager extends LitElement { #new-info > input { margin-right: 10px; } + + nwb-accordion { + margin-bottom: 0.5em; + } `; } @@ -161,7 +165,7 @@ export class InstanceManager extends LitElement { const id = path.slice(0, i + 1).join("/"); const accordion = this.#accordions[id]; target = target[path[i]]; // Progressively check the deeper nested instances - if (accordion) accordion.setSectionStatus(id, checkStatus(false, false, [...Object.values(target)])); + if (accordion) accordion.setStatus(checkStatus(false, false, [...Object.values(target)])); } }; @@ -299,11 +303,8 @@ export class InstanceManager extends LitElement { const list = this.#render(value, [...path, key]); const accordion = new Accordion({ - sections: { - [key]: { - content: list, - }, - }, + name: key, + content: list, contentPadding: "10px", }); diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index 05d5fdda8..9201e250c 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -146,9 +146,8 @@ const componentCSS = ` line-height: 1.4285em; } - [disabled] { - opacity: 0.5; - pointer-events: none; + nwb-accordion { + margin-bottom: 0.5em; } `; @@ -848,7 +847,10 @@ export class JSONSchemaForm extends LitElement { // Check properties that will be rendered before creating the accordion const base = [...this.base, ...localPath]; - const renderable = this.#getRenderable(info, required[name], base); + + const renderable = this.#getRenderable(info, required[name], localPath, true); + + const explicitlyRequired = schema.required?.includes(name) ?? false; if (renderable.length) { this.#nestedForms[name] = new JSONSchemaForm({ @@ -878,7 +880,7 @@ export class JSONSchemaForm extends LitElement { onThrow: (...args) => this.onThrow(...args), validateEmptyValues: this.validateEmptyValues, onStatusChange: (status) => { - accordion.setSectionStatus(headerName, status); + accordion.setStatus(status); this.checkStatus(); }, // Forward status changes to the parent form onInvalid: (...args) => this.onInvalid(...args), @@ -893,21 +895,33 @@ export class JSONSchemaForm extends LitElement { } if (!this.states[headerName]) this.states[headerName] = {}; - this.states[headerName].subtitle = `${ - this.#getRenderable(info, required[name], localPath, true).length - } fields`; - this.states[headerName].content = this.#nestedForms[name]; + + const enableToggle = document.createElement('input') + + Object.assign(enableToggle, { + type: 'checkbox', + checked: true, + style: 'margin-right: 10px; pointer-events:all;', + }) + + enableToggle.addEventListener('click', (e) => { + e.stopPropagation() + const { checked } = e.target + if (checked) accordion.info = { ...accordion.info, disabled: false } + else { + accordion.info = { ...accordion.info, disabled: true, open: false } + } + }) const accordion = new Accordion({ - sections: { - [headerName]: this.states[headerName], - }, + name: headerName, + subtitle:html`
${explicitlyRequired ? '' : enableToggle}${renderable.length ? `${renderable.length} fields` : ''}
`, + content: this.#nestedForms[name], + info: this.states[headerName] }); accordion.id = name; // assign name to accordion id - if (!renderable.length) accordion.setAttribute("disabled", ""); - return accordion; } From cf00ffda40e56ee54237b78528232c0e5333de9c Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 1 Nov 2023 14:16:30 -0700 Subject: [PATCH 02/21] Force accordion mode --- src/renderer/src/stories/Accordion.js | 42 +++++--- src/renderer/src/stories/JSONSchemaForm.js | 100 +++++++----------- .../src/stories/JSONSchemaForm.stories.js | 10 +- src/renderer/src/stories/pages/FormPage.js | 5 +- .../pages/guided-mode/data/GuidedMetadata.js | 1 - .../guided-mode/data/GuidedSourceData.js | 1 - .../stories/pages/settings/SettingsPage.js | 1 - 7 files changed, 74 insertions(+), 86 deletions(-) diff --git a/src/renderer/src/stories/Accordion.js b/src/renderer/src/stories/Accordion.js index 49deee89d..58c318b0c 100644 --- a/src/renderer/src/stories/Accordion.js +++ b/src/renderer/src/stories/Accordion.js @@ -111,10 +111,14 @@ export class Accordion extends LitElement { .guided--nav-bar-dropdown::after { font-size: 0.8em; position: absolute; - right: 50px; + right: 25px; font-family: ${unsafeCSS(emojiFontFamily)}; } + .guided--nav-bar-dropdown.toggleable::after { + right: 50px; + } + .guided--nav-bar-dropdown.error::after { content: "${errorSymbol}"; } @@ -127,7 +131,7 @@ export class Accordion extends LitElement { content: "${successSymbol}"; } - .guided--nav-bar-dropdown.content:hover { + .guided--nav-bar-dropdown.toggleable:hover { cursor: pointer; background-color: lightgray; } @@ -168,20 +172,20 @@ export class Accordion extends LitElement { }; } - constructor({ name, subtitle, content, info = {}, contentPadding } = {}) { + + constructor({ name, subtitle, toggleable= true, content, info = {}, contentPadding } = {}) { super(); this.name = name; this.subtitle = subtitle; this.content = content; this.info = info; + this.toggleable = toggleable; this.contentPadding = contentPadding; } updated() { if (!this.content) return - const isActive = this.info.open; - if (isActive) this.toggle(true); - else this.toggle(false); + this.toggle(!!this.info.open) } setStatus = (status) => { @@ -205,7 +209,8 @@ export class Accordion extends LitElement { const hasForce = forcedState !== undefined; const toggledState = !this.info.open; - let state = hasForce ? forcedState : toggledState; + const desiredState = hasForce ? forcedState : toggledState + const state = this.toOpen(desiredState); //remove hidden from child elements with guided--nav-bar-section-page class const section = this.shadowRoot.getElementById('section') @@ -216,32 +221,41 @@ export class Accordion extends LitElement { //toggle the chevron const chevron = dropdown.querySelector("nwb-chevron"); - chevron.direction = state ? "bottom" : "right"; + if (chevron) chevron.direction = state ? "bottom" : "right"; - this.info.open = state; + if (desiredState === state) this.info.open = state; // Update state if not overridden }; + toOpen = (state = this.info.open) => { + if (!this.toggleable) return true // Force open if not toggleable + else if (this.info.disabled) return false // Force closed if disabled + return state + } + render() { + + const isToggleable = this.content && this.toggleable; + return html`
${this.content ? html`