diff --git a/src/renderer/src/stories/Button.js b/src/renderer/src/stories/Button.js index 5a3fc4b35..cab7f0a8a 100644 --- a/src/renderer/src/stories/Button.js +++ b/src/renderer/src/stories/Button.js @@ -1,9 +1,11 @@ import { LitElement, html, css } from "lit"; import { styleMap } from "lit/directives/style-map.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; export class Button extends LitElement { - constructor({ primary, label, color = null, size, onClick, buttonStyles } = {}) { + constructor({ icon = null, primary, label, color = null, size, onClick, buttonStyles } = {}) { super(); + this.icon = icon; this.label = label; this.primary = primary; this.color = color; @@ -18,10 +20,6 @@ export class Button extends LitElement { display: inline-block; } - .button-icon { - margin-right: 10px; - } - .storybook-button { padding: 10px 15px; font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; @@ -29,9 +27,22 @@ export class Button extends LitElement { border: 0; border-radius: 5px; cursor: pointer; - display: inline-block; line-height: 1; + display: flex; + align-items: center; + justify-content: center; + } + + .button-icon { + margin-right: 7px; } + + svg { + margin: 0px !important; + width: auto; + height: 25px; + } + .storybook-button--primary { color: white; background-color: hsl(190, 60%, 36%); @@ -89,7 +100,11 @@ export class Button extends LitElement { )} @click=${this.onClick} > - ${this.icon ? html`${this.icon}` : ""} + ${this.icon + ? html`${this.icon instanceof HTMLElement ? this.icon : unsafeHTML(this.icon)}` + : ""} ${this.label ?? ""} `; diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index 1e80b594a..05d5fdda8 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -100,9 +100,9 @@ const componentCSS = ` margin: 15px; } -hr { - margin: 1em 0 1.5em 0; -} + hr { + margin: 1em 0 1.5em 0; + } pre { white-space: pre-wrap; /* Since CSS 2.1 */ @@ -137,14 +137,19 @@ hr { border-bottom: 1px solid gainsboro; } - .guided--text-input-instructions { - font-size: 13px; - width: 100%; - padding-top: 4px; - color: dimgray !important; - margin: 0 0 1em; - line-height: 1.4285em; -} + .guided--text-input-instructions { + font-size: 13px; + width: 100%; + padding-top: 4px; + color: dimgray !important; + margin: 0 0 1em; + line-height: 1.4285em; + } + + [disabled] { + opacity: 0.5; + pointer-events: none; + } `; document.addEventListener("dragover", (e) => { @@ -841,45 +846,51 @@ export class JSONSchemaForm extends LitElement { if (this.mode === "accordion" && hasMany) { const headerName = header(name); - this.#nestedForms[name] = new JSONSchemaForm({ - identifier: this.identifier, - schema: info, - results: { ...results[name] }, - globals: this.globals?.[name], - - states: this.states, - - mode: this.mode, - - onUpdate: (internalPath, value) => { - const path = [...localPath, ...internalPath]; - this.updateData(path, value); - }, - - required: required[name], // Scoped to the sub-schema - ignore: this.ignore, - dialogOptions: this.dialogOptions, - dialogType: this.dialogType, - onlyRequired: this.onlyRequired, - showLevelOverride: this.showLevelOverride, - deferLoading: this.deferLoading, - conditionalRequirements: this.conditionalRequirements, - validateOnChange: (...args) => this.validateOnChange(...args), - onThrow: (...args) => this.onThrow(...args), - validateEmptyValues: this.validateEmptyValues, - onStatusChange: (status) => { - accordion.setSectionStatus(headerName, status); - this.checkStatus(); - }, // Forward status changes to the parent form - onInvalid: (...args) => this.onInvalid(...args), - onLoaded: () => { - this.nLoaded++; - this.checkAllLoaded(); - }, - renderTable: (...args) => this.renderTable(...args), - onOverride: (...args) => this.onOverride(...args), - base: [...this.base, ...localPath], - }); + // Check properties that will be rendered before creating the accordion + const base = [...this.base, ...localPath]; + const renderable = this.#getRenderable(info, required[name], base); + + if (renderable.length) { + this.#nestedForms[name] = new JSONSchemaForm({ + identifier: this.identifier, + schema: info, + results: { ...results[name] }, + globals: this.globals?.[name], + + states: this.states, + + mode: this.mode, + + onUpdate: (internalPath, value) => { + const path = [...localPath, ...internalPath]; + this.updateData(path, value); + }, + + required: required[name], // Scoped to the sub-schema + ignore: this.ignore, + dialogOptions: this.dialogOptions, + dialogType: this.dialogType, + onlyRequired: this.onlyRequired, + showLevelOverride: this.showLevelOverride, + deferLoading: this.deferLoading, + conditionalRequirements: this.conditionalRequirements, + validateOnChange: (...args) => this.validateOnChange(...args), + onThrow: (...args) => this.onThrow(...args), + validateEmptyValues: this.validateEmptyValues, + onStatusChange: (status) => { + accordion.setSectionStatus(headerName, status); + this.checkStatus(); + }, // Forward status changes to the parent form + onInvalid: (...args) => this.onInvalid(...args), + onLoaded: () => { + this.nLoaded++; + this.checkAllLoaded(); + }, + renderTable: (...args) => this.renderTable(...args), + onOverride: (...args) => this.onOverride(...args), + base, + }); + } if (!this.states[headerName]) this.states[headerName] = {}; this.states[headerName].subtitle = `${ @@ -895,6 +906,8 @@ export class JSONSchemaForm extends LitElement { accordion.id = name; // assign name to accordion id + if (!renderable.length) accordion.setAttribute("disabled", ""); + return accordion; } diff --git a/src/renderer/src/stories/Main.js b/src/renderer/src/stories/Main.js index 70ed95cee..89e605dad 100644 --- a/src/renderer/src/stories/Main.js +++ b/src/renderer/src/stories/Main.js @@ -152,11 +152,11 @@ export class Main extends LitElement { style="position: sticky; padding: 0px 50px; top: 0; left: 0; background: white; z-index: 1;" >
-
+

${title}

${unsafeHTML(subtitle)}
-
${controls}
+
${controls}

` diff --git a/src/renderer/src/stories/assets/global.svg b/src/renderer/src/stories/assets/global.svg new file mode 100644 index 000000000..c3a0c9a3f --- /dev/null +++ b/src/renderer/src/stories/assets/global.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js index 4df29d12f..a67fa149a 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -17,6 +17,8 @@ import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; import { Button } from "../../../Button.js"; import { globalSchema } from "../../../../../../../schemas/base-metadata.schema"; +import globalIcon from "../../../assets/global.svg?raw"; + const propsToIgnore = [ "Ophys", // Always ignore ophys metadata (for now) "Icephys", // Always ignore icephys metadata (for now) @@ -51,6 +53,7 @@ export class GuidedMetadataPage extends ManagedPage { header = { controls: [ new Button({ + icon: globalIcon, label: "Edit Global Metadata", onClick: () => { this.#globalModal.form.results = merge(this.info.globalState.project, {}); @@ -92,7 +95,7 @@ export class GuidedMetadataPage extends ManagedPage { super.connectedCallback(); const modal = (this.#globalModal = createGlobalFormModal.call(this, { - header: "Edit Global Metadata", + header: "Global Metadata", propsToRemove: [...propsToIgnore], schema: globalSchema, // Provide HARDCODED global schema for metadata properties (not automatically abstracting across sessions)... hasInstances: true, diff --git a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js index cf4d00a3f..70fa3bf03 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -12,6 +12,8 @@ import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; import { header } from "../../../forms/utils"; import { Button } from "../../../Button.js"; +import globalIcon from "../../../assets/global.svg?raw"; + const propsToIgnore = [ "verbose", "es_key", @@ -33,7 +35,8 @@ export class GuidedSourceDataPage extends ManagedPage { header = { controls: [ new Button({ - label: "Edit Global Metadata", + icon: globalIcon, + label: "Edit Global Source Data", onClick: () => { this.#globalModal.form.results = merge(this.info.globalState.project?.SourceData, {}); this.#globalModal.open = true; @@ -162,7 +165,7 @@ export class GuidedSourceDataPage extends ManagedPage { connectedCallback() { super.connectedCallback(); const modal = (this.#globalModal = createGlobalFormModal.call(this, { - header: "Edit Global Source Data", + header: "Global Source Data", propsToRemove: [...propsToIgnore, "folder_path", "file_path"], key: "SourceData", schema: this.info.globalState.schema.source_data, diff --git a/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js b/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js index 1dedc3117..36a01d298 100644 --- a/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js +++ b/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js @@ -11,6 +11,8 @@ import { Button } from "../../../Button.js"; import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; import { header } from "../../../forms/utils"; +import globalIcon from "../../../assets/global.svg?raw"; + export class GuidedSubjectsPage extends Page { constructor(...args) { super(...args); @@ -20,6 +22,7 @@ export class GuidedSubjectsPage extends Page { subtitle: "Enter all metadata known about each experiment subject", controls: [ new Button({ + icon: globalIcon, label: "Edit Global Metadata", onClick: () => { this.#globalModal.form.results = merge(this.info.globalState.project?.Subject, {}); @@ -88,7 +91,7 @@ export class GuidedSubjectsPage extends Page { connectedCallback() { super.connectedCallback(); const modal = (this.#globalModal = createGlobalFormModal.call(this, { - header: "Edit Global Subject Data", + header: "Global Subject Metadata", key: "Subject", schema: globalSchema.properties.Subject, validateOnChange: (key, parent, path) => {