From 45602312ce403c2e1e32ce2cecc1c53c74865449 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Thu, 9 Nov 2023 07:52:56 -0800 Subject: [PATCH 001/133] Enable Ophys and fix table --- src/renderer/src/stories/SimpleTable.js | 41 ++++++++++++------- .../pages/guided-mode/data/GuidedMetadata.js | 2 +- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index 4835f3630..03b1d1d3e 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -1,14 +1,16 @@ -import { LitElement, css, html } from "lit"; +import { LitElement, css, html, unsafeCSS } from "lit"; import { header } from "./forms/utils"; import { checkStatus } from "../validation"; import { TableCell } from "./table/Cell"; import { ContextMenu } from "./table/ContextMenu"; -import { errorHue, warningHue } from "./globals"; +import { emojiFontFamily, errorHue, warningHue } from "./globals"; import { Loader } from "./Loader"; import { styleMap } from "lit/directives/style-map.js"; + import "./Button"; +import tippy from "tippy.js"; var isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; @@ -96,7 +98,6 @@ export class SimpleTable extends LitElement { } .table-container { - position: relative; overflow: auto; max-height: 400px; } @@ -110,6 +111,7 @@ export class SimpleTable extends LitElement { position: sticky; top: 0; left: 0; + z-index: 1; } th { @@ -140,15 +142,10 @@ export class SimpleTable extends LitElement { user-select: none; } - [title] .relative::after { - content: "ℹ️"; - cursor: help; - display: inline-block; + .relative .info { margin: 0px 5px; - text-align: center; font-size: 80%; - font-family: "Twemoji Mozilla", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", - "Noto Color Emoji", "EmojiOne Color", "Android Emoji", sans-serif; + font-family: ${unsafeCSS(emojiFontFamily)} } `; } @@ -584,11 +581,27 @@ export class SimpleTable extends LitElement { } } - #renderHeaderContent = (str) => html`
${str}
`; - #renderHeader = (str, { description }) => { - if (description) return html`${this.#renderHeaderContent(str)}`; - return html`${this.#renderHeaderContent(str)}`; + const header = document.createElement('th') + + // Inner Content + const div = document.createElement("div"); + div.classList.add("relative"); + const span = document.createElement("span"); + span.innerHTML = str + div.append(span); + header.append(div) + + // Add Description Tooltip + if (description) { + const span = document.createElement("span"); + span.classList.add("info"); + span.innerText = "ℹ️"; + div.append(span); + tippy(span, { content: `${description[0].toUpperCase() + description.slice(1)}` }); + } + + return header; }; #cells = []; 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 cb56c989b..2d843cd46 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -20,7 +20,7 @@ import { globalSchema } from "../../../../../../../schemas/base-metadata.schema" import globalIcon from "../../../assets/global.svg?raw"; const propsToIgnore = [ - "Ophys", // Always ignore ophys metadata (for now) + // "Ophys", // Always ignore ophys metadata (for now) "Icephys", // Always ignore icephys metadata (for now) "Behavior", // Always ignore behavior metadata (for now) // new RegExp("ndx-.+"), // Ignore all ndx extensions From 45aea759c18637d76bb42d85fc4bbdead9e3c2a4 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Thu, 9 Nov 2023 09:38:29 -0800 Subject: [PATCH 002/133] Add table in table --- src/renderer/src/stories/SimpleTable.js | 15 +- .../pages/guided-mode/data/GuidedMetadata.js | 2 + src/renderer/src/stories/table/Cell.ts | 17 ++- src/renderer/src/stories/table/ContextMenu.ts | 2 + src/renderer/src/stories/table/cells/base.ts | 70 ++++++++-- .../src/stories/table/cells/renderers/base.ts | 3 +- src/renderer/src/stories/table/cells/table.ts | 132 ++++++++++++++++++ 7 files changed, 221 insertions(+), 20 deletions(-) create mode 100644 src/renderer/src/stories/table/cells/table.ts diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index 03b1d1d3e..df5d86dcd 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -170,6 +170,7 @@ export class SimpleTable extends LitElement { onThrow, deferLoading, maxHeight, + contextOptions = {} } = {}) { super(); this.schema = schema ?? {}; @@ -180,6 +181,8 @@ export class SimpleTable extends LitElement { this.deferLoading = deferLoading ?? false; this.maxHeight = maxHeight ?? ""; + this.contextOptions = contextOptions; + if (validateOnChange) this.validateOnChange = validateOnChange; if (onStatusChange) this.onStatusChange = onStatusChange; if (onLoaded) this.onLoaded = onLoaded; @@ -187,6 +190,7 @@ export class SimpleTable extends LitElement { if (onUpdate) this.onUpdate = onUpdate; this.onmousedown = (ev) => { + ev.stopPropagation(); this.#clearSelected(); this.#selecting = true; const cell = this.#getCellFromEvent(ev); @@ -451,9 +455,12 @@ export class SimpleTable extends LitElement { if (options.column?.add) items.push(this.#menuOptions.column.add); if (options.column?.remove) items.push(this.#menuOptions.column.remove); - this.#context = new ContextMenu({ target: this.shadowRoot.querySelector("table"), items }); + this.#context = new ContextMenu({ + target: this.shadowRoot.querySelector("table"), + items, + }); - this.shadowRoot.append(this.#context); // Insert context menu + document.body.append(this.#context); // Insert context menu } #loaded = false; @@ -500,6 +507,7 @@ export class SimpleTable extends LitElement { add: true, remove: true, }, + ...this.contextOptions }); } }; @@ -664,6 +672,9 @@ export class SimpleTable extends LitElement { // Track the cell renderer const cell = new TableCell({ + info: { + col: this.colHeaders[info.j], + }, value, schema, validateOnChange: (value) => { 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 2d843cd46..55addad66 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -202,6 +202,8 @@ export class GuidedMetadataPage extends ManagedPage { onThrow, }); + setInterval(() => console.log(form.results.Ophys?.ImagingPlane[0]?.optical_channel.length), 1000) + return { subject, session, diff --git a/src/renderer/src/stories/table/Cell.ts b/src/renderer/src/stories/table/Cell.ts index e258e218f..16f976793 100644 --- a/src/renderer/src/stories/table/Cell.ts +++ b/src/renderer/src/stories/table/Cell.ts @@ -1,5 +1,7 @@ import { LitElement, css, html } from "lit" import { ArrayCell } from "./cells/array" +import { NestedTableCell } from "./cells/table" + import { TableCellBase } from "./cells/base" import { DateTimeCell } from "./cells/date-time" @@ -15,6 +17,7 @@ type OnValidateFunction = (info: ValidationResult) => void type TableCellProps = { value: string, + info: { col: string } schema: {[key: string]: any}, validateOnChange?: ValidationFunction, onValidate?: OnValidateFunction, @@ -23,6 +26,7 @@ type TableCellProps = { export class TableCell extends LitElement { declare schema: TableCellProps['schema'] + declare info: TableCellProps['info'] static get styles() { return css` @@ -63,10 +67,12 @@ export class TableCell extends LitElement { // } // } - constructor({ value, schema, validateOnChange, onValidate }: TableCellProps) { + constructor({ info, value, schema, validateOnChange, onValidate }: TableCellProps) { super() this.#value = value this.schema = schema + this.info = info + if (validateOnChange) this.validateOnChange = validateOnChange if (onValidate) this.onValidate = onValidate @@ -78,7 +84,6 @@ export class TableCell extends LitElement { } this.ondblclick = () => this.input.toggle(true) - document.addEventListener('click', () => this.input ? this.input.toggle(false) : false) } @@ -159,7 +164,11 @@ export class TableCell extends LitElement { this.interacted = false - if (this.schema.type === "array") cls = ArrayCell + if (this.schema.type === "array") { + const items = this.schema.items + if (items && items.type === "object") cls = NestedTableCell + else cls = ArrayCell + } else if (this.schema.format === "date-time") cls = DateTimeCell // Only actually rerender if new class type @@ -169,6 +178,8 @@ export class TableCell extends LitElement { if (this.input.interacted) this.interacted = true this.validate() }, + toggle: (v) => this.input ? this.input.toggle(v) : "", + info: this.info, schema: this.schema }) } diff --git a/src/renderer/src/stories/table/ContextMenu.ts b/src/renderer/src/stories/table/ContextMenu.ts index 9d20e6611..aa618dd0f 100644 --- a/src/renderer/src/stories/table/ContextMenu.ts +++ b/src/renderer/src/stories/table/ContextMenu.ts @@ -85,6 +85,8 @@ export class ContextMenu extends LitElement{ e.stopPropagation() this.#activePath = e.path || e.composedPath() this.style.display = 'block'; + + this.style.left = e.pageX + "px"; this.style.top = e.pageY + "px"; } diff --git a/src/renderer/src/stories/table/cells/base.ts b/src/renderer/src/stories/table/cells/base.ts index 3a52c7774..df7c5ccf1 100644 --- a/src/renderer/src/stories/table/cells/base.ts +++ b/src/renderer/src/stories/table/cells/base.ts @@ -2,6 +2,10 @@ import { LitElement, PropertyValueMap, css, html } from "lit" import { placeCaretAtEnd } from "../utils" type BaseTableProps = { + info: { + col: string, + }, + toggle: (state?: boolean) => void, schema: any, onOpen: Function, onClose: Function, @@ -47,20 +51,28 @@ export class TableCellBase extends LitElement { ` } - schema: any; + schema: BaseTableProps['schema']; + info: BaseTableProps['info']; + editToggle: BaseTableProps['toggle'] interacted = false constructor({ + info, schema, onOpen, onClose, - onChange + onChange, + toggle }: Partial = {}) { super() + this.info = info ?? {} this.schema = schema ?? {} + + this.editToggle = toggle ?? (() => {}); + if (onOpen) this.onOpen = onOpen if (onChange) this.onChange = onChange if (onClose) this.onClose = onClose @@ -79,20 +91,44 @@ export class TableCellBase extends LitElement { onClose: BaseTableProps['onClose'] = () => {} onChange: BaseTableProps['onChange'] = () => {} + #editableClose = () => this.editToggle(false) + toggle (state = !this.#active) { if (state === this.#active) return if (state) { - this.#editable.setAttribute('contenteditable', '') - this.setAttribute('editing', ''), + + this.setAttribute('editing', '') + + if (this.#editor === this.#editable) { + this.#editable.setAttribute('contenteditable', '') + this.#editable.style.pointerEvents = 'auto' + placeCaretAtEnd(this.#editable) + document.addEventListener('click', this.#editableClose) + } else { + this.#editor.onEditStart() + + } + this.onOpen() - this.#editable.style.pointerEvents = 'auto' - placeCaretAtEnd(this.#editable) + } else { - const current = this.getElementValue(this.#editable) + + let current this.removeAttribute('editing') + + if (this.#editor === this.#editable) { + current = this.getElementValue(this.#editable) + this.#editable.style.pointerEvents = '' + document.removeEventListener('click', this.#editableClose) + } else { + current = this.#editor.value + if (this.#editor && this.#editor.onEditEnd) this.#editor.onEditEnd() + } + this.onClose() - this.#editable.style.pointerEvents = '' + + this.setText( current ) } @@ -103,15 +139,20 @@ export class TableCellBase extends LitElement { #update(current: any) { let value = this.getValue(current) - if (this.value !== value) { + console.log('Updating', this.value, value) + // NOTE: Forcing change registration for all cells + // if (this.value !== value) { this.value = value this.onChange(value) - } + // } } setText(value: any, setOnInput = true) { if (setOnInput) [ this.#editor, this.#renderer ].forEach(el => this.setChild(el, value)) // RESETS HISTORY - this.#update(`${value}`) // Coerce to string + + if (this.schema.type === 'array') this.#update(value) // Ensure array values are not coerced + else this.#update(`${value}`) // Coerce to string + } @@ -168,8 +209,11 @@ export class TableCellBase extends LitElement { const editor = this.#editor = this.#render('editor') const renderer = this.#renderer = this.#render('renderer') - - this.addEventListener('blur', () => this.toggle(false)) + + this.addEventListener('blur', (ev) => { + ev.stopPropagation() + this.toggle(false) + }) if (!editor || !renderer || renderer === editor) return editor || renderer diff --git a/src/renderer/src/stories/table/cells/renderers/base.ts b/src/renderer/src/stories/table/cells/renderers/base.ts index 5d2b5c91e..16046a7cc 100644 --- a/src/renderer/src/stories/table/cells/renderers/base.ts +++ b/src/renderer/src/stories/table/cells/renderers/base.ts @@ -10,12 +10,11 @@ export class BaseRenderer extends LitElement { } } - constructor({ value = []} = {}) { + constructor({ value }: { value: any }) { super() this.value = value } - render() { return html`${this.value}` } diff --git a/src/renderer/src/stories/table/cells/table.ts b/src/renderer/src/stories/table/cells/table.ts new file mode 100644 index 000000000..b8b45438f --- /dev/null +++ b/src/renderer/src/stories/table/cells/table.ts @@ -0,0 +1,132 @@ +import { LitElement, css, html } from "lit"; +import { TableCellBase } from "./base"; +import { BaseRenderer } from "./renderers/base"; +import { Modal } from "../../Modal"; + +import { SimpleTable } from "../../SimpleTable.js"; +import { header } from "../../forms/utils"; + +export class NestedTableEditor extends LitElement { + + + static get styles() { + return [ + css` + ` + ] + } + + info: any + schema: any + toggle: any + + constructor(props: any) { + super(props) + this.schema = props.schema + this.info = props.info ?? {} + this.toggle = props.toggle + } + + #modal = new Modal({ + header: 'Table Editor', + onClose: () => this.toggle(false) + }) + + #table: SimpleTable + + onEditStart () { + const modal = this.#modal + + if (this.info.col) modal.header = `${header(this.info.col)} Table Editor` + + if (this.#table) this.#table.remove() + + + const div = document.createElement('div') + Object.assign(div.style, { + width: `${10}px`, + height: `${10}px`, + position: 'absolute', + zIndex: 1000, + background: 'red' + }) + + document.body.append(div) + + + const table = this.#table = new SimpleTable({ + schema: this.schema.items, + data: this.#value + }) + + table.style.padding = '25px 50px' + + modal.append(table) + + modal.open = true + } + + #value: any + + set value(value) { + this.#value = value + if (this.#table) this.#table.data = value + } + + get value() { + return this.#table.data ?? this.#value + } + + onEditEnd = () => { + this.#modal.open = false + Array.from(this.#modal.children).forEach(child => child.remove()) + } + + render() { + return this.#modal + } +} + +customElements.get("nwb-nested-table-cell-editor") || customElements.define("nwb-nested-table-cell-editor", NestedTableEditor); + + + +export class NestedTableRenderer extends BaseRenderer { + + + static get styles() { + return [ + css` + :host { + display: block; + text-align: center; + width: 100%; + } + ` + ] + } + + constructor(...args: any[]) { + super(...args) + } + + render() { + return html`Click to view table` + } +} + +customElements.get("nwb-nested-table-cell-renderer") || customElements.define("nwb-nested-table-cell-renderer", NestedTableRenderer); + + +export class NestedTableCell extends TableCellBase { + + constructor(props: any){ + super(props) + } + + renderer = new NestedTableRenderer({ value: this.value }) + + editor = new NestedTableEditor({ info: this.info, toggle: this.editToggle, value: this.value, schema: this.schema }) +} + +customElements.get("nwb-nested-table-cell") || customElements.define("nwb-nested-table-cell", NestedTableCell); From 67731fd4dd53785f47a9f40520648b69b957f147 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Thu, 9 Nov 2023 09:58:41 -0800 Subject: [PATCH 003/133] Update form with new data --- src/renderer/src/stories/JSONSchemaInput.js | 2 ++ src/renderer/src/stories/SimpleTable.js | 2 ++ .../stories/pages/guided-mode/data/GuidedMetadata.js | 2 -- src/renderer/src/stories/table/Cell.ts | 10 ++++++++-- src/renderer/src/stories/table/cells/base.ts | 12 ++++++------ src/renderer/src/stories/table/cells/table.ts | 8 -------- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 4ad9a8742..b67027d4d 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -148,6 +148,7 @@ export class JSONSchemaInput extends LitElement { #validationTimeout = null; #updateData = (fullPath, value, forceUpdate) => { + this.onUpdate ? this.onUpdate(value) : this.form ? this.form.updateData(fullPath, value, forceUpdate) : ""; const path = [...fullPath]; @@ -223,6 +224,7 @@ export class JSONSchemaInput extends LitElement { if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat); // Create tables if possible else if (itemSchema.type === "object" && this.form.createTable) { + const tableMetadata = { schema: itemSchema, data: this.value, diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index 0b37569a3..7679d97cc 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -722,6 +722,8 @@ export class SimpleTable extends LitElement { if (this.#selecting) this.#selectCells(cell); }; + td.ondblclick = () => cell.toggle(true); + td.appendChild(cell); return td; }; 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 83071aaec..080b99566 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -203,8 +203,6 @@ export class GuidedMetadataPage extends ManagedPage { onThrow, }); - setInterval(() => console.log(form.results.Ophys?.ImagingPlane[0]?.optical_channel.length), 1000) - return { subject, session, diff --git a/src/renderer/src/stories/table/Cell.ts b/src/renderer/src/stories/table/Cell.ts index c49749463..82e843d29 100644 --- a/src/renderer/src/stories/table/Cell.ts +++ b/src/renderer/src/stories/table/Cell.ts @@ -38,6 +38,7 @@ export class TableCell extends LitElement { white-space: nowrap; color: black; font-size: 13px; + height: 100%; } :host > * { @@ -85,10 +86,15 @@ export class TableCell extends LitElement { this.#validator = (value) => regex.test(value) } - this.ondblclick = () => this.input.toggle(true) + this.ondblclick = (ev) => { + ev.stopPropagation() + this.toggle(true) + } } + toggle = (v: boolean) => this.input.toggle(v) + get value() { return this.input ? this.input.getValue() : this.#value } @@ -181,7 +187,7 @@ export class TableCell extends LitElement { if (this.input.interacted) this.interacted = true this.validate() }, - toggle: (v) => this.input ? this.input.toggle(v) : "", + toggle: (v) => this.toggle(v), info: this.info, schema: this.schema }) diff --git a/src/renderer/src/stories/table/cells/base.ts b/src/renderer/src/stories/table/cells/base.ts index df7c5ccf1..d22c5dec6 100644 --- a/src/renderer/src/stories/table/cells/base.ts +++ b/src/renderer/src/stories/table/cells/base.ts @@ -92,6 +92,7 @@ export class TableCellBase extends LitElement { onChange: BaseTableProps['onChange'] = () => {} #editableClose = () => this.editToggle(false) + #originalEditorValue = undefined toggle (state = !this.#active) { if (state === this.#active) return @@ -107,7 +108,6 @@ export class TableCellBase extends LitElement { document.addEventListener('click', this.#editableClose) } else { this.#editor.onEditStart() - } this.onOpen() @@ -123,6 +123,7 @@ export class TableCellBase extends LitElement { document.removeEventListener('click', this.#editableClose) } else { current = this.#editor.value + this.interacted = true if (this.#editor && this.#editor.onEditEnd) this.#editor.onEditEnd() } @@ -137,20 +138,19 @@ export class TableCellBase extends LitElement { getValue = (input: any = this.value) => input // Process inputs from the editor - #update(current: any) { + #update(current: any, forceUpdate = false) { let value = this.getValue(current) - console.log('Updating', this.value, value) // NOTE: Forcing change registration for all cells - // if (this.value !== value) { + if (this.value !== value || forceUpdate) { this.value = value this.onChange(value) - // } + } } setText(value: any, setOnInput = true) { if (setOnInput) [ this.#editor, this.#renderer ].forEach(el => this.setChild(el, value)) // RESETS HISTORY - if (this.schema.type === 'array') this.#update(value) // Ensure array values are not coerced + if (this.schema.type === 'array') this.#update(value, true) // Ensure array values are not coerced else this.#update(`${value}`) // Coerce to string } diff --git a/src/renderer/src/stories/table/cells/table.ts b/src/renderer/src/stories/table/cells/table.ts index b8b45438f..e1b710699 100644 --- a/src/renderer/src/stories/table/cells/table.ts +++ b/src/renderer/src/stories/table/cells/table.ts @@ -8,14 +8,6 @@ import { header } from "../../forms/utils"; export class NestedTableEditor extends LitElement { - - static get styles() { - return [ - css` - ` - ] - } - info: any schema: any toggle: any From 23c90db8ed07f198e821fbe3a083a447edfb0ebf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 18:05:00 +0000 Subject: [PATCH 004/133] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/JSONSchemaInput.js | 2 -- src/renderer/src/stories/SimpleTable.js | 20 +++++++++---------- .../guided-mode/data/GuidedPathExpansion.js | 2 +- .../guided-mode/data/GuidedSourceData.js | 7 +++---- .../stories/pages/guided-mode/data/utils.js | 2 +- src/renderer/src/stories/table/Cell.ts | 2 +- src/renderer/src/stories/table/cells/base.ts | 8 ++++---- src/renderer/src/stories/table/cells/table.ts | 8 ++++---- 8 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index b67027d4d..4ad9a8742 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -148,7 +148,6 @@ export class JSONSchemaInput extends LitElement { #validationTimeout = null; #updateData = (fullPath, value, forceUpdate) => { - this.onUpdate ? this.onUpdate(value) : this.form ? this.form.updateData(fullPath, value, forceUpdate) : ""; const path = [...fullPath]; @@ -224,7 +223,6 @@ export class JSONSchemaInput extends LitElement { if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat); // Create tables if possible else if (itemSchema.type === "object" && this.form.createTable) { - const tableMetadata = { schema: itemSchema, data: this.value, diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index 7679d97cc..16437262b 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -145,7 +145,7 @@ export class SimpleTable extends LitElement { .relative .info { margin: 0px 5px; font-size: 80%; - font-family: ${unsafeCSS(emojiFontFamily)} + font-family: ${unsafeCSS(emojiFontFamily)}; } `; } @@ -170,7 +170,7 @@ export class SimpleTable extends LitElement { onThrow, deferLoading, maxHeight, - contextOptions = {} + contextOptions = {}, } = {}) { super(); this.schema = schema ?? {}; @@ -455,8 +455,8 @@ export class SimpleTable extends LitElement { if (options.column?.add) items.push(this.#menuOptions.column.add); if (options.column?.remove) items.push(this.#menuOptions.column.remove); - this.#context = new ContextMenu({ - target: this.shadowRoot.querySelector("table"), + this.#context = new ContextMenu({ + target: this.shadowRoot.querySelector("table"), items, }); @@ -507,7 +507,7 @@ export class SimpleTable extends LitElement { add: true, remove: true, }, - ...this.contextOptions + ...this.contextOptions, }); } }; @@ -590,15 +590,15 @@ export class SimpleTable extends LitElement { } #renderHeader = (str, { description }) => { - const header = document.createElement('th') + const header = document.createElement("th"); // Inner Content const div = document.createElement("div"); div.classList.add("relative"); const span = document.createElement("span"); - span.innerHTML = str + span.innerHTML = str; div.append(span); - header.append(div) + header.append(div); // Add Description Tooltip if (description) { @@ -672,7 +672,7 @@ export class SimpleTable extends LitElement { // Track the cell renderer const cell = new TableCell({ - info: { + info: { col: this.colHeaders[info.j], }, value, @@ -723,7 +723,7 @@ export class SimpleTable extends LitElement { }; td.ondblclick = () => cell.toggle(true); - + td.appendChild(cell); return td; }; diff --git a/src/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js b/src/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js index 90af0a741..3b8fe1c1b 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js @@ -129,7 +129,7 @@ export class GuidedPathExpansionPage extends Page { const source_data = {}; for (let key in globalState.interfaces) { - const existing = existingSourceData?.[key] + const existing = existingSourceData?.[key]; if (existing) source_data[key] = existing ?? {}; } 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 01cb001ea..431b4aab7 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -69,7 +69,7 @@ export class GuidedSourceDataPage extends ManagedPage { backdrop: "rgba(0,0,0, 0.4)", timerProgressBar: false, didOpen: () => { - Swal.showLoading(); + Swal.showLoading(); }, }); }; @@ -80,7 +80,6 @@ export class GuidedSourceDataPage extends ManagedPage { await Promise.all( Object.values(this.forms).map(async ({ subject, session, form }) => { - const info = this.info.globalState.results[subject][session]; // NOTE: This clears all user-defined results @@ -187,8 +186,8 @@ export class GuidedSourceDataPage extends ManagedPage { const modal = (this.#globalModal = createGlobalFormModal.call(this, { header: "Global Source Data", propsToRemove: [ - ...propsToIgnore, - "folder_path", + ...propsToIgnore, + "folder_path", "file_path", // NOTE: Still keeping plural path specifications for now ], diff --git a/src/renderer/src/stories/pages/guided-mode/data/utils.js b/src/renderer/src/stories/pages/guided-mode/data/utils.js index 44ac392dd..668aa7988 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/utils.js +++ b/src/renderer/src/stories/pages/guided-mode/data/utils.js @@ -2,7 +2,7 @@ import { merge } from "../../utils.js"; // Merge project-wide data into metadata export function populateWithProjectMetadata(info, globalState) { - const copy = structuredClone(info) + const copy = structuredClone(info); const toMerge = Object.entries(globalState.project).filter(([_, value]) => value && typeof value === "object"); toMerge.forEach(([key, value]) => { let internalMetadata = copy[key]; diff --git a/src/renderer/src/stories/table/Cell.ts b/src/renderer/src/stories/table/Cell.ts index 82e843d29..92ae57d9c 100644 --- a/src/renderer/src/stories/table/Cell.ts +++ b/src/renderer/src/stories/table/Cell.ts @@ -75,7 +75,7 @@ export class TableCell extends LitElement { this.#value = value this.schema = schema this.info = info - + if (validateOnChange) this.validateOnChange = validateOnChange if (onValidate) this.onValidate = onValidate diff --git a/src/renderer/src/stories/table/cells/base.ts b/src/renderer/src/stories/table/cells/base.ts index d22c5dec6..66d0b68d4 100644 --- a/src/renderer/src/stories/table/cells/base.ts +++ b/src/renderer/src/stories/table/cells/base.ts @@ -2,7 +2,7 @@ import { LitElement, PropertyValueMap, css, html } from "lit" import { placeCaretAtEnd } from "../utils" type BaseTableProps = { - info: { + info: { col: string, }, toggle: (state?: boolean) => void, @@ -70,7 +70,7 @@ export class TableCellBase extends LitElement { this.info = info ?? {} this.schema = schema ?? {} - + this.editToggle = toggle ?? (() => {}); if (onOpen) this.onOpen = onOpen @@ -123,7 +123,7 @@ export class TableCellBase extends LitElement { document.removeEventListener('click', this.#editableClose) } else { current = this.#editor.value - this.interacted = true + this.interacted = true if (this.#editor && this.#editor.onEditEnd) this.#editor.onEditEnd() } @@ -209,7 +209,7 @@ export class TableCellBase extends LitElement { const editor = this.#editor = this.#render('editor') const renderer = this.#renderer = this.#render('renderer') - + this.addEventListener('blur', (ev) => { ev.stopPropagation() this.toggle(false) diff --git a/src/renderer/src/stories/table/cells/table.ts b/src/renderer/src/stories/table/cells/table.ts index e1b710699..aac54219e 100644 --- a/src/renderer/src/stories/table/cells/table.ts +++ b/src/renderer/src/stories/table/cells/table.ts @@ -25,7 +25,7 @@ export class NestedTableEditor extends LitElement { }) #table: SimpleTable - + onEditStart () { const modal = this.#modal @@ -74,7 +74,7 @@ export class NestedTableEditor extends LitElement { Array.from(this.#modal.children).forEach(child => child.remove()) } - render() { + render() { return this.#modal } } @@ -102,7 +102,7 @@ export class NestedTableRenderer extends BaseRenderer { super(...args) } - render() { + render() { return html`Click to view table` } } @@ -117,7 +117,7 @@ export class NestedTableCell extends TableCellBase { } renderer = new NestedTableRenderer({ value: this.value }) - + editor = new NestedTableEditor({ info: this.info, toggle: this.editToggle, value: this.value, schema: this.schema }) } From c6c1a23fbb73c829cde90481e8234a88eaace25a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:52:11 +0000 Subject: [PATCH 005/133] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/pages/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/stories/pages/utils.js b/src/renderer/src/stories/pages/utils.js index d926792e9..fd1d65e6e 100644 --- a/src/renderer/src/stories/pages/utils.js +++ b/src/renderer/src/stories/pages/utils.js @@ -45,7 +45,8 @@ export function merge(toMerge = {}, target = {}, mergeOpts = {}) { for (const [k, v] of Object.entries(toMerge)) { const targetV = target[k]; // if (isPrivate(k)) continue; - if (mergeOpts.arrays && Array.isArray(v) && Array.isArray(targetV)) target[k] = [...targetV, ...v]; // Merge array entries together + if (mergeOpts.arrays && Array.isArray(v) && Array.isArray(targetV)) + target[k] = [...targetV, ...v]; // Merge array entries together else if (v === undefined) { delete target[k]; // Remove matched values // if (mergeOpts.remove !== false) delete target[k]; // Remove matched values From 6eeea2627a0ae21920b9f72810b36bd3dc429bb1 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sat, 11 Nov 2023 10:13:15 -0500 Subject: [PATCH 006/133] Improve ignore specificity --- src/renderer/src/stories/JSONSchemaForm.js | 28 ++++++------- .../src/stories/forms/GlobalFormModal.ts | 39 ++++++++++++++---- src/renderer/src/stories/pages/FormPage.js | 2 +- .../pages/guided-mode/data/GuidedMetadata.js | 33 ++++++++++----- .../guided-mode/data/GuidedSourceData.js | 41 ++++++++++++------- 5 files changed, 97 insertions(+), 46 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index 04bc6d906..e6cf5d382 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -209,7 +209,7 @@ export class JSONSchemaForm extends LitElement { this.results = (props.base ? structuredClone(props.results) : props.results) ?? {}; // Deep clone results in nested forms this.globals = props.globals ?? {}; - this.ignore = props.ignore ?? []; + this.ignore = props.ignore ?? {}; this.required = props.required ?? {}; this.dialogOptions = props.dialogOptions; this.dialogType = props.dialogType; @@ -585,7 +585,7 @@ export class JSONSchemaForm extends LitElement { // } // }; - #getRenderable = (schema = {}, required, path, recursive = false) => { + #getRenderable = (schema = {}, required, ignore = {}, path, recursive = false) => { const entries = Object.entries(schema.properties ?? {}); const isArrayOfArrays = (arr) => !!arr.find((v) => Array.isArray(v)); @@ -601,20 +601,20 @@ export class JSONSchemaForm extends LitElement { }; const isRenderable = (key, value) => { - if (recursive && value.properties) return this.#getRenderable(value, required[key], [...path, key], true); + + if (recursive && value.properties) return this.#getRenderable(value, required[key], { + ...ignore[key], + "*": {...ignore["*"] ?? {}, ...ignore[key]["*"] ?? {}}, // Accumulate ignore values + }, [...path, key], true); + else return [key, value]; }; const res = entries .map(([key, value]) => { if (!value.properties && key === "definitions") return false; // Skip definitions - if ( - this.ignore.find((v) => { - if (typeof v === "string") return v === key; - else return v.test(key); - }) - ) - return false; + if (this.ignore["*"]?.[key]) return false; // Skip all properties with this name + else if (this.ignore[key] === true) return false; // Skip this property if (this.showLevelOverride >= path.length) return isRenderable(key, value); if (required[key]) return isRenderable(key, value); if (this.#getLink([...this.base, ...path, key])) return isRenderable(key, value); @@ -824,10 +824,10 @@ export class JSONSchemaForm extends LitElement { #accordions = {}; - #render = (schema, results, required = {}, path = []) => { + #render = (schema, results, required = {}, ignore = {}, path = []) => { let isLink = Symbol("isLink"); // Filter non-required properties (if specified) and render the sub-schema - const renderable = this.#getRenderable(schema, required, path); + const renderable = this.#getRenderable(schema, required, ignore, path); // // Filter non-required properties (if specified) and render the sub-schema // const renderable = path.length ? this.#getRenderable(schema, required) : Object.entries(schema.properties ?? {}) @@ -934,7 +934,7 @@ export class JSONSchemaForm extends LitElement { const headerName = header(name); - const renderableInside = this.#getRenderable(info, required[name], localPath, true); + const renderableInside = this.#getRenderable(info, required[name], ignore, localPath, true); const __disabled = this.results.__disabled ?? (this.results.__disabled = {}); const __interacted = __disabled.__interacted ?? (__disabled.__interacted = {}); @@ -1140,7 +1140,7 @@ export class JSONSchemaForm extends LitElement { ? html`

Description

${unsafeHTML(schema.description)}

` : ""} - ${this.#render(schema, this.resolved, this.#requirements)} + ${this.#render(schema, this.resolved, this.#requirements, this.ignore)} `; } } diff --git a/src/renderer/src/stories/forms/GlobalFormModal.ts b/src/renderer/src/stories/forms/GlobalFormModal.ts index f6de21e8b..0e3ea2f3c 100644 --- a/src/renderer/src/stories/forms/GlobalFormModal.ts +++ b/src/renderer/src/stories/forms/GlobalFormModal.ts @@ -7,12 +7,20 @@ import { onThrow } from "../../errors"; import { merge } from "../pages/utils.js"; import { save } from "../../progress/index.js"; +type SingleIgnorePropsLevel = { + [x:string]: true, +} + +type IgnorePropsLevel = { + ["*"]?: SingleIgnorePropsLevel, + [x:string]: true | IgnorePropsLevel, +} export function createGlobalFormModal(this: Page, { header, schema, - propsToIgnore = [], - propsToRemove = [], + propsToIgnore = {}, + propsToRemove = {}, key, hasInstances = false, validateOnChange, @@ -20,8 +28,8 @@ export function createGlobalFormModal(this: Page, { }: { header: string schema: any - propsToIgnore?: string[] - propsToRemove?: string[] + propsToIgnore?: IgnorePropsLevel + propsToRemove?: IgnorePropsLevel key?: string, hasInstances?: boolean validateOnChange?: Function, @@ -37,11 +45,28 @@ export function createGlobalFormModal(this: Page, { const schemaCopy = structuredClone(schema) // Ensure no mutation - function removeProperties(obj: any, props: string[]) { - props.forEach(prop => delete obj[prop]) + + function removeProperties(obj: any, props: IgnorePropsLevel = {}, extraGlobalProps?: SingleIgnorePropsLevel) { + + if (extraGlobalProps && Object.keys(extraGlobalProps).length > 0) { + if (props["*"]) props["*"] = { ...props["*"], ...extraGlobalProps } + else props["*"] = extraGlobalProps + } + + const globals = props["*"] ?? {} + + // First remove all global properties + Object.entries(globals).forEach(([key, value]) => (value === true) ? delete obj[key] : '') + + // Full pass for explicit removal + Object.entries(props).forEach(([key, value]) => { + if (key === '*') return + if (value === true) delete obj[key] + else removeProperties(obj[key], value, globals) + }) } - if (hasInstances) Object.keys(schemaCopy.properties).forEach(i => removeProperties(schemaCopy.properties[i].properties, propsToRemove)) + if (hasInstances) Object.keys(schemaCopy.properties).forEach(key => removeProperties(schemaCopy.properties[key].properties, propsToRemove[key], propsToRemove["*"])) else removeProperties(schemaCopy.properties, propsToRemove) const globalForm = new JSONSchemaForm({ diff --git a/src/renderer/src/stories/pages/FormPage.js b/src/renderer/src/stories/pages/FormPage.js index 650a5f2e2..76b9068e0 100644 --- a/src/renderer/src/stories/pages/FormPage.js +++ b/src/renderer/src/stories/pages/FormPage.js @@ -33,7 +33,7 @@ export function schemaToPages(schema, globalStatePath, options, transformationCa }) ); - if (optionsCopy.ignore && optionsCopy.ignore.includes(key)) return null; + if (optionsCopy.ignore && optionsCopy.ignore[key]) return null; return page; }) .filter((page) => page); 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 c805d1daf..f1c4456e2 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -19,15 +19,28 @@ 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) - "Behavior", // Always ignore behavior metadata (for now) - // new RegExp("ndx-.+"), // Ignore all ndx extensions - "ndx-dandi-icephys", - "subject_id", - "session_id", -]; +const propsToIgnore = { + "Ophys": true, // Always ignore ophys metadata (for now) + "Icephys": true, // Always ignore icephys metadata (for now) + "Behavior": true, // Always ignore behavior metadata (for now) + "ndx-dandi-icephys": true, + "Subject": { + "subject_id": true, + }, + "NWBFile": { + "session_id": true, + } +} + +// const propsToIgnore = [ +// // "Ophys", // Always ignore ophys metadata (for now) +// "Icephys", // Always ignore icephys metadata (for now) +// "Behavior", // Always ignore behavior metadata (for now) +// // new RegExp("ndx-.+"), // Ignore all ndx extensions +// "ndx-dandi-icephys", +// "subject_id", +// "session_id", +// ]; import { preprocessMetadataSchema } from "../../../../../../../schemas/base-metadata.schema"; @@ -97,7 +110,7 @@ export class GuidedMetadataPage extends ManagedPage { const modal = (this.#globalModal = createGlobalFormModal.call(this, { header: "Global Metadata", - propsToRemove: [...propsToIgnore], + propsToRemove: propsToIgnore, schema: globalSchema, // Provide HARDCODED global schema for metadata properties (not automatically abstracting across sessions)... hasInstances: true, mergeFunction: function (globalResolved, globals) { 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 b725f134d..189aaf014 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -15,14 +15,25 @@ import { Button } from "../../../Button.js"; import globalIcon from "../../../assets/global.svg?raw"; import { run } from "../options/utils.js"; -const propsToIgnore = [ - "verbose", - "es_key", - "exclude_shanks", - "load_sync_channel", - "stream_id", // NOTE: May be desired for other interfaces - "nsx_override", -]; +// const propsToIgnore = [ +// "verbose", +// "es_key", +// "exclude_shanks", +// "load_sync_channel", +// "stream_id", // NOTE: May be desired for other interfaces +// "nsx_override", +// ]; + +const propsToIgnore = { + "*": { + "verbose": true, + "es_key": true, + "exclude_shanks": true, + "load_sync_channel": true, + "stream_id": true, // NOTE: May be desired for other interfaces + "nsx_override": true, + } +} export class GuidedSourceDataPage extends ManagedPage { constructor(...args) { @@ -185,12 +196,14 @@ export class GuidedSourceDataPage extends ManagedPage { super.connectedCallback(); const modal = (this.#globalModal = createGlobalFormModal.call(this, { header: "Global Source Data", - propsToRemove: [ - ...propsToIgnore, - "folder_path", - "file_path", - // NOTE: Still keeping plural path specifications for now - ], + propsToRemove: { + "*": { + ...propsToIgnore["*"], + "folder_path": true, + "file_path": true, + // NOTE: Still keeping plural path specifications for now + } + }, key: "SourceData", schema: this.info.globalState.schema.source_data, hasInstances: true, From 91134a61f74e029036f7d11a04fa57c35b97344b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 15:13:35 +0000 Subject: [PATCH 007/133] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/JSONSchemaForm.js | 17 +++++++++----- .../src/stories/forms/GlobalFormModal.ts | 2 +- .../pages/guided-mode/data/GuidedMetadata.js | 18 +++++++-------- .../guided-mode/data/GuidedSourceData.js | 22 +++++++++---------- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index e6cf5d382..4ae234828 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -601,12 +601,17 @@ export class JSONSchemaForm extends LitElement { }; const isRenderable = (key, value) => { - - if (recursive && value.properties) return this.#getRenderable(value, required[key], { - ...ignore[key], - "*": {...ignore["*"] ?? {}, ...ignore[key]["*"] ?? {}}, // Accumulate ignore values - }, [...path, key], true); - + if (recursive && value.properties) + return this.#getRenderable( + value, + required[key], + { + ...ignore[key], + "*": { ...(ignore["*"] ?? {}), ...(ignore[key]["*"] ?? {}) }, // Accumulate ignore values + }, + [...path, key], + true + ); else return [key, value]; }; diff --git a/src/renderer/src/stories/forms/GlobalFormModal.ts b/src/renderer/src/stories/forms/GlobalFormModal.ts index 0e3ea2f3c..38ab3ac80 100644 --- a/src/renderer/src/stories/forms/GlobalFormModal.ts +++ b/src/renderer/src/stories/forms/GlobalFormModal.ts @@ -45,7 +45,7 @@ export function createGlobalFormModal(this: Page, { const schemaCopy = structuredClone(schema) // Ensure no mutation - + function removeProperties(obj: any, props: IgnorePropsLevel = {}, extraGlobalProps?: SingleIgnorePropsLevel) { if (extraGlobalProps && Object.keys(extraGlobalProps).length > 0) { 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 f1c4456e2..30f4eedf8 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -20,17 +20,17 @@ import { globalSchema } from "../../../../../../../schemas/base-metadata.schema" import globalIcon from "../../../assets/global.svg?raw"; const propsToIgnore = { - "Ophys": true, // Always ignore ophys metadata (for now) - "Icephys": true, // Always ignore icephys metadata (for now) - "Behavior": true, // Always ignore behavior metadata (for now) + Ophys: true, // Always ignore ophys metadata (for now) + Icephys: true, // Always ignore icephys metadata (for now) + Behavior: true, // Always ignore behavior metadata (for now) "ndx-dandi-icephys": true, - "Subject": { - "subject_id": true, + Subject: { + subject_id: true, }, - "NWBFile": { - "session_id": true, - } -} + NWBFile: { + session_id: true, + }, +}; // const propsToIgnore = [ // // "Ophys", // Always ignore ophys metadata (for now) 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 189aaf014..14144e2bb 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -26,14 +26,14 @@ import { run } from "../options/utils.js"; const propsToIgnore = { "*": { - "verbose": true, - "es_key": true, - "exclude_shanks": true, - "load_sync_channel": true, - "stream_id": true, // NOTE: May be desired for other interfaces - "nsx_override": true, - } -} + verbose: true, + es_key: true, + exclude_shanks: true, + load_sync_channel: true, + stream_id: true, // NOTE: May be desired for other interfaces + nsx_override: true, + }, +}; export class GuidedSourceDataPage extends ManagedPage { constructor(...args) { @@ -199,10 +199,10 @@ export class GuidedSourceDataPage extends ManagedPage { propsToRemove: { "*": { ...propsToIgnore["*"], - "folder_path": true, - "file_path": true, + folder_path: true, + file_path: true, // NOTE: Still keeping plural path specifications for now - } + }, }, key: "SourceData", schema: this.info.globalState.schema.source_data, From ffcc1f161319cc9202385ac0e0c04065d4244bd8 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sat, 11 Nov 2023 11:00:19 -0500 Subject: [PATCH 008/133] Properly ignore properties in tables --- schemas/base-metadata.schema.ts | 45 +++++++++++++++++++ src/renderer/src/stories/BasicTable.js | 6 +++ src/renderer/src/stories/JSONSchemaForm.js | 20 +++++++-- src/renderer/src/stories/JSONSchemaInput.js | 9 +++- src/renderer/src/stories/SimpleTable.js | 6 +++ src/renderer/src/stories/Table.js | 5 +++ .../pages/guided-mode/data/GuidedMetadata.js | 19 ++++++-- 7 files changed, 102 insertions(+), 8 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 372979e22..6e3887926 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -1,5 +1,6 @@ import baseMetadataSchema from './json/base_metadata_schema.json' assert { type: "json" } + export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { // Add unit to weight @@ -17,6 +18,50 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { // Override description of keywords schema.properties.NWBFile.properties.keywords.description = 'Terms to describe your dataset (e.g. Neural circuits, V1, etc.)' // Add description to keywords + + + const ophys = schema.properties.Ophys + + if (ophys) { + + ophys.properties.TwoPhotonSeries.order = [ + "name", + "description", + "scan_line_rate", + "field_of_view", + "unit", + "conversion", + "offset" + ] + + ophys.properties.ImagingPlane.order = [ + "name", + "description", + "device", + "optic_channel" + ] + } + + + Object.entries(schema.properties).forEach(([key, value]) => { + + const defs = value.properties.definitions ?? {} + + Object.entries(value.properties).forEach(([k, v]) => { + + if (k ==='definitions') return + + // Uniformly grab definitions + const ref = defs[k] ?? v.items ?? v + if (!ref.properties) return + Object.keys(ref.properties).forEach(k => { + const info = ref.properties[k] + if (info.description && info.description.includes('DEPRECATED')) delete ref.properties[k] // Remove deprecated properties + }) + }) + }) + + return schema } diff --git a/src/renderer/src/stories/BasicTable.js b/src/renderer/src/stories/BasicTable.js index 5d3396c32..351bb2045 100644 --- a/src/renderer/src/stories/BasicTable.js +++ b/src/renderer/src/stories/BasicTable.js @@ -105,6 +105,7 @@ export class BasicTable extends LitElement { constructor({ name, schema, + ignore, data, keyColumn, maxHeight, @@ -121,6 +122,8 @@ export class BasicTable extends LitElement { this.maxHeight = maxHeight ?? ""; this.validateEmptyCells = validateEmptyCells ?? true; + this.ignore = ignore ?? {}; + if (validateOnChange) this.validateOnChange = validateOnChange; if (onStatusChange) this.onStatusChange = onStatusChange; if (onLoaded) this.onLoaded = onLoaded; @@ -340,6 +343,9 @@ export class BasicTable extends LitElement { const entries = { ...this.schema.properties }; + for (let key in this.ignore) delete entries[key] + for (let key in (this.ignore["*"] ?? {})) delete entries[key] + // Add existing additional properties to the entries variable if necessary if (this.schema.additionalProperties) { Object.values(this.data).reduce((acc, v) => { diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index e6cf5d382..f36119aec 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -16,6 +16,21 @@ const isObject = (o) => { return o && typeof o === "object" && !Array.isArray(o); }; + +export const getIgnore = (o, path) => { + if (typeof path === "string") path = path.split("."); + return path.reduce((acc, key) => { + + const info = acc[key] ?? {}; + + return { + ...info, + "*": {...(acc["*"] ?? {}), ...(info["*"] ?? {})}, // Accumulate ignore values + } + + }, o) +} + const selfRequiredSymbol = Symbol(); const componentCSS = ` @@ -602,10 +617,7 @@ export class JSONSchemaForm extends LitElement { const isRenderable = (key, value) => { - if (recursive && value.properties) return this.#getRenderable(value, required[key], { - ...ignore[key], - "*": {...ignore["*"] ?? {}, ...ignore[key]["*"] ?? {}}, // Accumulate ignore values - }, [...path, key], true); + if (recursive && value.properties) return this.#getRenderable(value, required[key], getIgnore(ignore, key), [...path, key], true); else return [key, value]; }; diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 4ad9a8742..48a7daf64 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -10,7 +10,7 @@ import { List } from "./List"; import { Modal } from "./Modal"; import { capitalize } from "./forms/utils"; -import { JSONSchemaForm } from "./JSONSchemaForm"; +import { JSONSchemaForm, getIgnore } from "./JSONSchemaForm"; const isFilesystemSelector = (name, format) => { if (Array.isArray(format)) return format.map((f) => isFilesystemSelector(name, f)).every(Boolean) ? format : null; @@ -223,10 +223,15 @@ export class JSONSchemaInput extends LitElement { if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat); // Create tables if possible else if (itemSchema.type === "object" && this.form.createTable) { + + const ignore = this.form?.ignore ? getIgnore(this.form?.ignore, [...this.form.base, ...path, name]) : {} + const tableMetadata = { schema: itemSchema, data: this.value, + ignore, + onUpdate: () => this.#updateData(fullPath, tableMetadata.data, true), // Ensure change propagates to all forms // NOTE: This is likely an incorrect declaration of the table validation call @@ -254,6 +259,8 @@ export class JSONSchemaInput extends LitElement { }; const table = this.form.createTable(name, tableMetadata, fullPath); // Try creating table. Otherwise use nested form + + if (table) return (this.form.tables[name] = table === true ? new BasicTable(tableMetadata) : table); } diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index 16437262b..4af959ec4 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -171,6 +171,7 @@ export class SimpleTable extends LitElement { deferLoading, maxHeight, contextOptions = {}, + ignore = {} } = {}) { super(); this.schema = schema ?? {}; @@ -181,6 +182,8 @@ export class SimpleTable extends LitElement { this.deferLoading = deferLoading ?? false; this.maxHeight = maxHeight ?? ""; + this.ignore = ignore + this.contextOptions = contextOptions; if (validateOnChange) this.validateOnChange = validateOnChange; @@ -736,6 +739,9 @@ export class SimpleTable extends LitElement { const entries = (this.#schema = { ...this.schema.properties }); + for (let key in this.ignore) delete entries[key] + for (let key in (this.ignore["*"] ?? {})) delete entries[key] + // Add existing additional properties to the entries variable if necessary if (this.schema.additionalProperties) { Object.values(this.data).reduce((acc, v) => { diff --git a/src/renderer/src/stories/Table.js b/src/renderer/src/stories/Table.js index e88abb036..432943807 100644 --- a/src/renderer/src/stories/Table.js +++ b/src/renderer/src/stories/Table.js @@ -78,6 +78,7 @@ export class Table extends LitElement { onStatusChange, onThrow, contextMenu, + ignore } = {}) { super(); this.schema = schema ?? {}; @@ -86,6 +87,7 @@ export class Table extends LitElement { this.globals = globals ?? {}; this.validateEmptyCells = validateEmptyCells ?? true; this.contextMenu = contextMenu ?? {}; + this.ignore = ignore ?? {}; if (onThrow) this.onThrow = onThrow; if (onUpdate) this.onUpdate = onUpdate; @@ -172,6 +174,9 @@ export class Table extends LitElement { const entries = { ...this.schema.properties }; + for (let key in this.ignore) delete entries[key] + for (let key in (this.ignore["*"] ?? {})) delete entries[key] + // Add existing additional properties to the entries variable if necessary if (this.schema.additionalProperties) { Object.values(this.data).reduce((acc, v) => { 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 f1c4456e2..3b00be4de 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -20,7 +20,22 @@ import { globalSchema } from "../../../../../../../schemas/base-metadata.schema" import globalIcon from "../../../assets/global.svg?raw"; const propsToIgnore = { - "Ophys": true, // Always ignore ophys metadata (for now) + "Ophys": { + "ImagingPlane": { + "manifold": true, + "unit": true, + "conversion": true, + }, + "TwoPhotonSeries": { + "format": true, + "starting_frame": true, + "starting_time": true, + "rate": true, + "control": true, + "control_description": true, + "device": true + } + }, // Always ignore ophys metadata (for now) "Icephys": true, // Always ignore icephys metadata (for now) "Behavior": true, // Always ignore behavior metadata (for now) "ndx-dandi-icephys": true, @@ -169,8 +184,6 @@ export class GuidedMetadataPage extends ManagedPage { resolveResults(subject, session, globalState); - console.log(subject, session, results); - // Create the form const form = new JSONSchemaForm({ identifier: instanceId, From e84e8e9f33fdbb76aa74cbcab713affc1bdeaa53 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 16:01:17 +0000 Subject: [PATCH 009/133] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- schemas/base-metadata.schema.ts | 14 +++++----- src/renderer/src/stories/BasicTable.js | 4 +-- src/renderer/src/stories/JSONSchemaForm.js | 16 +++++------- src/renderer/src/stories/JSONSchemaInput.js | 8 +++--- src/renderer/src/stories/SimpleTable.js | 8 +++--- src/renderer/src/stories/Table.js | 6 ++--- .../pages/guided-mode/data/GuidedMetadata.js | 26 +++++++++---------- 7 files changed, 39 insertions(+), 43 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 6e3887926..3e7c03b27 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -18,12 +18,12 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { // Override description of keywords schema.properties.NWBFile.properties.keywords.description = 'Terms to describe your dataset (e.g. Neural circuits, V1, etc.)' // Add description to keywords - + const ophys = schema.properties.Ophys if (ophys) { - + ophys.properties.TwoPhotonSeries.order = [ "name", "description", @@ -40,28 +40,28 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { "device", "optic_channel" ] - } + } Object.entries(schema.properties).forEach(([key, value]) => { const defs = value.properties.definitions ?? {} - + Object.entries(value.properties).forEach(([k, v]) => { if (k ==='definitions') return // Uniformly grab definitions const ref = defs[k] ?? v.items ?? v - if (!ref.properties) return + if (!ref.properties) return Object.keys(ref.properties).forEach(k => { const info = ref.properties[k] if (info.description && info.description.includes('DEPRECATED')) delete ref.properties[k] // Remove deprecated properties }) }) }) - - + + return schema } diff --git a/src/renderer/src/stories/BasicTable.js b/src/renderer/src/stories/BasicTable.js index 351bb2045..5563f3740 100644 --- a/src/renderer/src/stories/BasicTable.js +++ b/src/renderer/src/stories/BasicTable.js @@ -343,8 +343,8 @@ export class BasicTable extends LitElement { const entries = { ...this.schema.properties }; - for (let key in this.ignore) delete entries[key] - for (let key in (this.ignore["*"] ?? {})) delete entries[key] + for (let key in this.ignore) delete entries[key]; + for (let key in this.ignore["*"] ?? {}) delete entries[key]; // Add existing additional properties to the entries variable if necessary if (this.schema.additionalProperties) { diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index f36119aec..51e6e5484 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -16,20 +16,17 @@ const isObject = (o) => { return o && typeof o === "object" && !Array.isArray(o); }; - export const getIgnore = (o, path) => { if (typeof path === "string") path = path.split("."); return path.reduce((acc, key) => { - const info = acc[key] ?? {}; return { ...info, - "*": {...(acc["*"] ?? {}), ...(info["*"] ?? {})}, // Accumulate ignore values - } - - }, o) -} + "*": { ...(acc["*"] ?? {}), ...(info["*"] ?? {}) }, // Accumulate ignore values + }; + }, o); +}; const selfRequiredSymbol = Symbol(); @@ -616,9 +613,8 @@ export class JSONSchemaForm extends LitElement { }; const isRenderable = (key, value) => { - - if (recursive && value.properties) return this.#getRenderable(value, required[key], getIgnore(ignore, key), [...path, key], true); - + if (recursive && value.properties) + return this.#getRenderable(value, required[key], getIgnore(ignore, key), [...path, key], true); else return [key, value]; }; diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 48a7daf64..4e67f36af 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -223,14 +223,15 @@ export class JSONSchemaInput extends LitElement { if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat); // Create tables if possible else if (itemSchema.type === "object" && this.form.createTable) { + const ignore = this.form?.ignore + ? getIgnore(this.form?.ignore, [...this.form.base, ...path, name]) + : {}; - const ignore = this.form?.ignore ? getIgnore(this.form?.ignore, [...this.form.base, ...path, name]) : {} - const tableMetadata = { schema: itemSchema, data: this.value, - ignore, + ignore, onUpdate: () => this.#updateData(fullPath, tableMetadata.data, true), // Ensure change propagates to all forms @@ -260,7 +261,6 @@ export class JSONSchemaInput extends LitElement { const table = this.form.createTable(name, tableMetadata, fullPath); // Try creating table. Otherwise use nested form - if (table) return (this.form.tables[name] = table === true ? new BasicTable(tableMetadata) : table); } diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index 4af959ec4..815993e89 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -171,7 +171,7 @@ export class SimpleTable extends LitElement { deferLoading, maxHeight, contextOptions = {}, - ignore = {} + ignore = {}, } = {}) { super(); this.schema = schema ?? {}; @@ -182,7 +182,7 @@ export class SimpleTable extends LitElement { this.deferLoading = deferLoading ?? false; this.maxHeight = maxHeight ?? ""; - this.ignore = ignore + this.ignore = ignore; this.contextOptions = contextOptions; @@ -739,8 +739,8 @@ export class SimpleTable extends LitElement { const entries = (this.#schema = { ...this.schema.properties }); - for (let key in this.ignore) delete entries[key] - for (let key in (this.ignore["*"] ?? {})) delete entries[key] + for (let key in this.ignore) delete entries[key]; + for (let key in this.ignore["*"] ?? {}) delete entries[key]; // Add existing additional properties to the entries variable if necessary if (this.schema.additionalProperties) { diff --git a/src/renderer/src/stories/Table.js b/src/renderer/src/stories/Table.js index 432943807..46c741f27 100644 --- a/src/renderer/src/stories/Table.js +++ b/src/renderer/src/stories/Table.js @@ -78,7 +78,7 @@ export class Table extends LitElement { onStatusChange, onThrow, contextMenu, - ignore + ignore, } = {}) { super(); this.schema = schema ?? {}; @@ -174,8 +174,8 @@ export class Table extends LitElement { const entries = { ...this.schema.properties }; - for (let key in this.ignore) delete entries[key] - for (let key in (this.ignore["*"] ?? {})) delete entries[key] + for (let key in this.ignore) delete entries[key]; + for (let key in this.ignore["*"] ?? {}) delete entries[key]; // Add existing additional properties to the entries variable if necessary if (this.schema.additionalProperties) { 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 9727a05aa..c33fbda6d 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -21,20 +21,20 @@ import globalIcon from "../../../assets/global.svg?raw"; const propsToIgnore = { Ophys: { - "ImagingPlane": { - "manifold": true, - "unit": true, - "conversion": true, + ImagingPlane: { + manifold: true, + unit: true, + conversion: true, + }, + TwoPhotonSeries: { + format: true, + starting_frame: true, + starting_time: true, + rate: true, + control: true, + control_description: true, + device: true, }, - "TwoPhotonSeries": { - "format": true, - "starting_frame": true, - "starting_time": true, - "rate": true, - "control": true, - "control_description": true, - "device": true - } }, // Always ignore ophys metadata (for now) Icephys: true, // Always ignore icephys metadata (for now) Behavior: true, // Always ignore behavior metadata (for now) From 31b01765f7dbc00853d130d0575baa531a780aea Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sat, 11 Nov 2023 16:40:35 -0500 Subject: [PATCH 010/133] Allow ordering the table columns --- schemas/base-metadata.schema.ts | 6 +- src/renderer/src/stories/BasicTable.js | 12 ++-- src/renderer/src/stories/SimpleTable.js | 16 +++--- src/renderer/src/stories/Table.js | 74 +++++++++++++++---------- 4 files changed, 61 insertions(+), 47 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 3e7c03b27..3ba3a15b9 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -24,7 +24,7 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { if (ophys) { - ophys.properties.TwoPhotonSeries.order = [ + ophys.properties.definitions.TwoPhotonSeries.order = [ "name", "description", "scan_line_rate", @@ -34,11 +34,11 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { "offset" ] - ophys.properties.ImagingPlane.order = [ + ophys.properties.definitions.ImagingPlane.order = [ "name", "description", "device", - "optic_channel" + "optical_channel" ] } diff --git a/src/renderer/src/stories/BasicTable.js b/src/renderer/src/stories/BasicTable.js index 5563f3740..7af4eb123 100644 --- a/src/renderer/src/stories/BasicTable.js +++ b/src/renderer/src/stories/BasicTable.js @@ -363,14 +363,10 @@ export class BasicTable extends LitElement { // Sort Columns by Key Column and Requirement const keys = (this.#keys = - this.colHeaders = - Object.keys(entries).sort((a, b) => { - if (a === this.keyColumn) return -1; - if (b === this.keyColumn) return 1; - if (entries[a].required && !entries[b].required) return -1; - if (!entries[a].required && entries[b].required) return 1; - return 0; - })); + this.colHeaders = sortTable({ + ...this.schema, + properties: entries + }, this.keyColumn, this.schema.order)); // Try to guess the key column if unspecified if (!Array.isArray(this.data) && !this.keyColumn) { diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index 815993e89..3bdef91cc 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -11,6 +11,7 @@ import { styleMap } from "lit/directives/style-map.js"; import "./Button"; import tippy from "tippy.js"; +import { sortTable } from "./Table"; var isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; @@ -757,13 +758,12 @@ export class SimpleTable extends LitElement { } // Sort Columns by Key Column and Requirement - const keys = (this.colHeaders = Object.keys(entries).sort((a, b) => { - if (a === this.keyColumn) return -1; - if (b === this.keyColumn) return 1; - if (entries[a].required && !entries[b].required) return -1; - if (!entries[a].required && entries[b].required) return 1; - return 0; - })); + this.colHeaders = sortTable({ + ...this.schema, + properties: entries + }, this.keyColumn, this.schema.order) + + // Try to guess the key column if unspecified if (!Array.isArray(this.data) && !this.keyColumn) { @@ -778,7 +778,7 @@ export class SimpleTable extends LitElement { - ${[...keys].map(header).map((str, i) => this.#renderHeader(str, entries[keys[i]]))} + ${[...this.colHeaders].map(header).map((str, i) => this.#renderHeader(str, entries[this.colHeaders[i]]))} diff --git a/src/renderer/src/stories/Table.js b/src/renderer/src/stories/Table.js index 46c741f27..f40da4863 100644 --- a/src/renderer/src/stories/Table.js +++ b/src/renderer/src/stories/Table.js @@ -10,6 +10,43 @@ import "tippy.js/dist/tippy.css"; const maxRows = 20; +const isRequired = (col, schema) => { + return schema.required?.includes(col); +}; + +export function sortTable(schema, keyColumn, order) { + + const cols = Object.keys(schema.properties) + + //Sort alphabetically + .sort((a, b) => { + if (a < b) return -1; + if (a > b) return 1; + return 0; + }) + .sort((a, b) => { + const aRequired = isRequired(a, schema); + const bRequired = isRequired(b, schema); + if (aRequired && bRequired) return 0; + if (aRequired) return -1; + if (bRequired) return 1; + return 0; + }) + .sort((a, b) => { + if (a === keyColumn) return -1; + if (b === keyColumn) return 1; + return 0; + }); + + return order ? cols.sort((a,b) => { + const idxA = order.indexOf(a); + const idxB = order.indexOf(b); + if (idxA === -1) return 1; + if (idxB === -1) return -1; + return idxA - idxB; + }) : cols; +} + // Inject scoped stylesheet const styles = ` @@ -163,10 +200,6 @@ export class Table extends LitElement { onOverride = () => {}; onThrow = () => {}; - isRequired = (col) => { - return this.schema?.required?.includes(col); - }; - updated() { const div = (this.shadowRoot ?? this).querySelector("div"); @@ -192,26 +225,11 @@ export class Table extends LitElement { } // Sort Columns by Key Column and Requirement - const colHeaders = (this.colHeaders = Object.keys(entries) - .sort((a, b) => { - //Sort alphabetically - if (a < b) return -1; - if (a > b) return 1; - return 0; - }) - .sort((a, b) => { - const aRequired = this.isRequired(a); - const bRequired = this.isRequired(b); - if (aRequired && bRequired) return 0; - if (aRequired) return -1; - if (bRequired) return 1; - return 0; - }) - .sort((a, b) => { - if (a === this.keyColumn) return -1; - if (b === this.keyColumn) return 1; - return 0; - })); + + const colHeaders = this.colHeaders = sortTable({ + ...this.schema, + properties: entries, + }, this.keyColumn, this.schema.order); // Try to guess the key column if unspecified if (!Array.isArray(this.data) && !this.keyColumn) { @@ -272,7 +290,7 @@ export class Table extends LitElement { }; let ogThis = this; - const isRequired = this.isRequired(k); + const required = isRequired(k, this.schema); const validator = async function (value, callback) { const validateEmptyCells = ogThis.validateEmptyCells; @@ -310,7 +328,7 @@ export class Table extends LitElement { return; } - if (!value && isRequired) { + if (!value && required) { ogThis.#handleValidationResult( [{ message: `${header(k)} is a required property.`, type: "error" }], this.row, @@ -356,8 +374,8 @@ export class Table extends LitElement { const rel = TH.querySelector(".relative"); - const isRequired = this.isRequired(col); - if (isRequired) + const required = isRequired(col, this.schema); + if (required) rel.setAttribute( "data-required", this.validateEmptyCells From 0a3809ebf6182dad3f9476bc2ba7bee85444e7f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:40:52 +0000 Subject: [PATCH 011/133] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/BasicTable.js | 13 +++-- src/renderer/src/stories/SimpleTable.js | 18 ++++--- src/renderer/src/stories/Table.js | 69 +++++++++++++------------ 3 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/renderer/src/stories/BasicTable.js b/src/renderer/src/stories/BasicTable.js index 7af4eb123..9b08973fa 100644 --- a/src/renderer/src/stories/BasicTable.js +++ b/src/renderer/src/stories/BasicTable.js @@ -363,10 +363,15 @@ export class BasicTable extends LitElement { // Sort Columns by Key Column and Requirement const keys = (this.#keys = - this.colHeaders = sortTable({ - ...this.schema, - properties: entries - }, this.keyColumn, this.schema.order)); + this.colHeaders = + sortTable( + { + ...this.schema, + properties: entries, + }, + this.keyColumn, + this.schema.order + )); // Try to guess the key column if unspecified if (!Array.isArray(this.data) && !this.keyColumn) { diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index 3bdef91cc..953a95871 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -758,12 +758,14 @@ export class SimpleTable extends LitElement { } // Sort Columns by Key Column and Requirement - this.colHeaders = sortTable({ - ...this.schema, - properties: entries - }, this.keyColumn, this.schema.order) - - + this.colHeaders = sortTable( + { + ...this.schema, + properties: entries, + }, + this.keyColumn, + this.schema.order + ); // Try to guess the key column if unspecified if (!Array.isArray(this.data) && !this.keyColumn) { @@ -778,7 +780,9 @@ export class SimpleTable extends LitElement {
- ${[...this.colHeaders].map(header).map((str, i) => this.#renderHeader(str, entries[this.colHeaders[i]]))} + ${[...this.colHeaders] + .map(header) + .map((str, i) => this.#renderHeader(str, entries[this.colHeaders[i]]))} diff --git a/src/renderer/src/stories/Table.js b/src/renderer/src/stories/Table.js index f40da4863..1ecbd9dc0 100644 --- a/src/renderer/src/stories/Table.js +++ b/src/renderer/src/stories/Table.js @@ -15,36 +15,37 @@ const isRequired = (col, schema) => { }; export function sortTable(schema, keyColumn, order) { - const cols = Object.keys(schema.properties) - //Sort alphabetically - .sort((a, b) => { - if (a < b) return -1; - if (a > b) return 1; - return 0; - }) - .sort((a, b) => { - const aRequired = isRequired(a, schema); - const bRequired = isRequired(b, schema); - if (aRequired && bRequired) return 0; - if (aRequired) return -1; - if (bRequired) return 1; - return 0; - }) - .sort((a, b) => { - if (a === keyColumn) return -1; - if (b === keyColumn) return 1; - return 0; - }); - - return order ? cols.sort((a,b) => { - const idxA = order.indexOf(a); - const idxB = order.indexOf(b); - if (idxA === -1) return 1; - if (idxB === -1) return -1; - return idxA - idxB; - }) : cols; + //Sort alphabetically + .sort((a, b) => { + if (a < b) return -1; + if (a > b) return 1; + return 0; + }) + .sort((a, b) => { + const aRequired = isRequired(a, schema); + const bRequired = isRequired(b, schema); + if (aRequired && bRequired) return 0; + if (aRequired) return -1; + if (bRequired) return 1; + return 0; + }) + .sort((a, b) => { + if (a === keyColumn) return -1; + if (b === keyColumn) return 1; + return 0; + }); + + return order + ? cols.sort((a, b) => { + const idxA = order.indexOf(a); + const idxB = order.indexOf(b); + if (idxA === -1) return 1; + if (idxB === -1) return -1; + return idxA - idxB; + }) + : cols; } // Inject scoped stylesheet @@ -226,10 +227,14 @@ export class Table extends LitElement { // Sort Columns by Key Column and Requirement - const colHeaders = this.colHeaders = sortTable({ - ...this.schema, - properties: entries, - }, this.keyColumn, this.schema.order); + const colHeaders = (this.colHeaders = sortTable( + { + ...this.schema, + properties: entries, + }, + this.keyColumn, + this.schema.order + )); // Try to guess the key column if unspecified if (!Array.isArray(this.data) && !this.keyColumn) { From 9d5babf08c16977c384acbaf203b878fff17a4c5 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sat, 11 Nov 2023 17:06:59 -0500 Subject: [PATCH 012/133] Max height of table cell and other updates --- schemas/base-metadata.schema.ts | 5 +---- .../src/stories/pages/guided-mode/data/GuidedMetadata.js | 3 +++ src/renderer/src/stories/table/Cell.ts | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 3ba3a15b9..cf767717d 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -28,10 +28,7 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { "name", "description", "scan_line_rate", - "field_of_view", - "unit", - "conversion", - "offset" + "field_of_view" ] ophys.properties.definitions.ImagingPlane.order = [ 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 c168bbe12..aed8d3f66 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -34,6 +34,9 @@ const propsToIgnore = { control: true, control_description: true, device: true, + unit: true, + conversion: true, + offset: true }, }, // Always ignore ophys metadata (for now) Icephys: true, // Always ignore icephys metadata (for now) diff --git a/src/renderer/src/stories/table/Cell.ts b/src/renderer/src/stories/table/Cell.ts index 92ae57d9c..856c698df 100644 --- a/src/renderer/src/stories/table/Cell.ts +++ b/src/renderer/src/stories/table/Cell.ts @@ -35,10 +35,11 @@ export class TableCell extends LitElement { :host { display: flex; - white-space: nowrap; color: black; font-size: 13px; height: 100%; + max-height: 100px; + overflow-y: scroll; } :host > * { From 1fe98367d6762aab9ebe93d54769a6ac23f6eb71 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 22:08:59 +0000 Subject: [PATCH 013/133] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../src/stories/pages/guided-mode/data/GuidedMetadata.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 aed8d3f66..d39787601 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -36,7 +36,7 @@ const propsToIgnore = { device: true, unit: true, conversion: true, - offset: true + offset: true, }, }, // Always ignore ophys metadata (for now) Icephys: true, // Always ignore icephys metadata (for now) From 9f7891ca046d05700eeeb3fa177a14abf4cfebe4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 22:23:53 +0000 Subject: [PATCH 014/133] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/JSONSchemaForm.js | 1 - src/renderer/src/stories/pages/guided-mode/data/utils.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index 74249b98e..5ea687737 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -294,7 +294,6 @@ export class JSONSchemaForm extends LitElement { // Track resolved values for the form (data only) updateData(localPath, value, forceUpdate = false) { - const path = [...localPath]; const name = path.pop(); diff --git a/src/renderer/src/stories/pages/guided-mode/data/utils.js b/src/renderer/src/stories/pages/guided-mode/data/utils.js index 60b38f93e..4618e17ba 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/utils.js +++ b/src/renderer/src/stories/pages/guided-mode/data/utils.js @@ -40,7 +40,6 @@ export function resolveProperties(properties = {}, target, globals = {}) { const props = info.properties; if (!(name in target)) { - if (target.__disabled?.[name]) continue; // Skip disabled properties if (props) target[name] = {}; // Regisiter new interfaces in results From 3d454e957ed8643df3cfb255bad12ccba58192ba Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sat, 11 Nov 2023 17:28:36 -0500 Subject: [PATCH 015/133] Allow segmentation --- schemas/base-metadata.schema.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index cf767717d..083728880 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -24,14 +24,16 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { if (ophys) { - ophys.properties.definitions.TwoPhotonSeries.order = [ + const defs = ophys.properties.definitions + + if (defs.TwoPhotonSeries) defs.TwoPhotonSeries.order = [ "name", "description", "scan_line_rate", "field_of_view" ] - ophys.properties.definitions.ImagingPlane.order = [ + if (defs.ImagingPlane) defs.ImagingPlane.order = [ "name", "description", "device", From d49756545ea70e90c7dda381d02aa9f7404401e0 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sat, 11 Nov 2023 17:29:25 -0500 Subject: [PATCH 016/133] Update GuidedMetadata.js --- .../src/stories/pages/guided-mode/data/GuidedMetadata.js | 3 +++ 1 file changed, 3 insertions(+) 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 d39787601..ab961064c 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -33,6 +33,9 @@ const propsToIgnore = { rate: true, control: true, control_description: true, + comments: true, + resolution: true, + dimension: true, device: true, unit: true, conversion: true, From ce8d56d4bcd74e138f93107ccd43edb1ec1c00e3 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sat, 11 Nov 2023 17:35:54 -0500 Subject: [PATCH 017/133] Update base-metadata.schema.ts --- schemas/base-metadata.schema.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 083728880..986727b72 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -24,16 +24,16 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { if (ophys) { - const defs = ophys.properties.definitions + const getProp = (name: string) => ophys.properties.definitions[name] ?? ophys.properties[name] - if (defs.TwoPhotonSeries) defs.TwoPhotonSeries.order = [ + if (getProp("TwoPhotonSeries")) getProp("TwoPhotonSeries").order = [ "name", "description", "scan_line_rate", "field_of_view" ] - if (defs.ImagingPlane) defs.ImagingPlane.order = [ + if (getProp("ImagingPlane")) getProp("ImagingPlane").order = [ "name", "description", "device", From 296a17939eda680c1e93c0a005f1c6c88303017c Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sat, 11 Nov 2023 17:37:29 -0500 Subject: [PATCH 018/133] Update base-metadata.schema.ts --- schemas/base-metadata.schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 986727b72..21511673b 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -24,7 +24,7 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { if (ophys) { - const getProp = (name: string) => ophys.properties.definitions[name] ?? ophys.properties[name] + const getProp = (name: string) => ophys.properties.definitions?.[name] ?? ophys.properties[name] if (getProp("TwoPhotonSeries")) getProp("TwoPhotonSeries").order = [ "name", From 66916bb33bc30dcce4b24b4f32228f52145a44e5 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sun, 12 Nov 2023 08:06:24 -0500 Subject: [PATCH 019/133] Update BasicTable.js --- src/renderer/src/stories/BasicTable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/stories/BasicTable.js b/src/renderer/src/stories/BasicTable.js index 9b08973fa..003b415de 100644 --- a/src/renderer/src/stories/BasicTable.js +++ b/src/renderer/src/stories/BasicTable.js @@ -7,6 +7,7 @@ import { errorHue, warningHue } from "./globals"; import * as promises from "../promises"; import "./Button"; +import { sortTable } from "./Table"; export class BasicTable extends LitElement { static get styles() { From b6cefb27bb3ea50d8bfe14c3d703172d90434151 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:52:46 +0000 Subject: [PATCH 020/133] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/pages/Page.js | 2 +- .../src/stories/pages/guided-mode/options/utils.js | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/renderer/src/stories/pages/Page.js b/src/renderer/src/stories/pages/Page.js index a0aee18bc..8207918c2 100644 --- a/src/renderer/src/stories/pages/Page.js +++ b/src/renderer/src/stories/pages/Page.js @@ -170,7 +170,7 @@ export class Page extends LitElement { Object.assign(element.style, { textAlign: "left", display: "block", - }) + }); const progressBar = new ProgressBar(); elements.progress = progressBar; diff --git a/src/renderer/src/stories/pages/guided-mode/options/utils.js b/src/renderer/src/stories/pages/guided-mode/options/utils.js index 238681e36..83bef4acc 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/utils.js +++ b/src/renderer/src/stories/pages/guided-mode/options/utils.js @@ -23,12 +23,10 @@ export const openProgressSwal = (options, callback) => { }; export const run = async (url, payload, options = {}) => { + let internalSwal; - let internalSwal - - if (options.swal === false) {} - else if (!options.swal || options.swal === true) { - + if (options.swal === false) { + } else if (!options.swal || options.swal === true) { if (!("showCancelButton" in options)) { options.showCancelButton = true; options.customClass = { actions: "swal-conversion-actions" }; @@ -40,12 +38,12 @@ export const run = async (url, payload, options = {}) => { signal: cancelController.signal, }; - const popup = internalSwal = await openProgressSwal(options, (result) => { + const popup = (internalSwal = await openProgressSwal(options, (result) => { if (!result.isConfirmed) cancelController.abort(); }).then(async (swal) => { if (options.onOpen) await options.onOpen(swal); return swal; - }); + })); const element = popup.getHtmlContainer(); const actions = popup.getActions(); From 5450bf93f0b13ba9cf0fe885426886cbb812b951 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Wed, 29 Nov 2023 10:31:30 -0800 Subject: [PATCH 021/133] Update sorting --- schemas/base-metadata.schema.ts | 7 ++++--- src/renderer/src/stories/SimpleTable.js | 4 ++-- .../src/stories/pages/guided-mode/data/GuidedMetadata.js | 3 +++ .../src/stories/pages/guided-mode/results/GuidedResults.js | 2 -- src/renderer/src/stories/pages/settings/SettingsPage.js | 2 -- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 283c665bb..2e7f9863c 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -80,16 +80,17 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, globa if (ophys) { - const getProp = (name: string) => ophys.properties.definitions?.[name] ?? ophys.properties[name] + const getProp = (name: string, base = true) => base ? ophys.properties[name] : ophys.properties.definitions?.[name] - if (getProp("TwoPhotonSeries")) getProp("TwoPhotonSeries").order = [ + if (getProp("TwoPhotonSeries")) getProp("TwoPhotonSeries").items.order = [ "name", "description", "scan_line_rate", "field_of_view" ] - if (getProp("ImagingPlane")) getProp("ImagingPlane").order = [ + + if (getProp("ImagingPlane")) getProp("ImagingPlane").items.order = [ "name", "description", "device", diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index 953a95871..582fb91b5 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -743,8 +743,8 @@ export class SimpleTable extends LitElement { for (let key in this.ignore) delete entries[key]; for (let key in this.ignore["*"] ?? {}) delete entries[key]; - // Add existing additional properties to the entries variable if necessary - if (this.schema.additionalProperties) { + // Add existing additional / pattern properties to the entries variable if necessary + if (this.schema.additionalProperties !== false || this.schema.patternProperties) { Object.values(this.data).reduce((acc, v) => { Object.keys(v).forEach((k) => !(k in entries) 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 1873fd331..13e876b5f 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -18,14 +18,17 @@ import { Button } from "../../../Button.js"; import globalIcon from "../../../assets/global.svg?raw"; +const imagingPlaneKey = 'imaging_plane'; const propsToIgnore = { Ophys: { ImagingPlane: { + [imagingPlaneKey]: true, manifold: true, unit: true, conversion: true, }, TwoPhotonSeries: { + [imagingPlaneKey]: true, format: true, starting_frame: true, starting_time: true, diff --git a/src/renderer/src/stories/pages/guided-mode/results/GuidedResults.js b/src/renderer/src/stories/pages/guided-mode/results/GuidedResults.js index 34f95a16f..06665b3b3 100644 --- a/src/renderer/src/stories/pages/guided-mode/results/GuidedResults.js +++ b/src/renderer/src/stories/pages/guided-mode/results/GuidedResults.js @@ -11,8 +11,6 @@ export class GuidedResultsPage extends Page { render() { const { conversion } = this.info.globalState; - console.log(this.info.globalState); - if (!conversion) return html`

Your conversion failed. Please try again.

`; diff --git a/src/renderer/src/stories/pages/settings/SettingsPage.js b/src/renderer/src/stories/pages/settings/SettingsPage.js index 3266bc508..a4c7c4b25 100644 --- a/src/renderer/src/stories/pages/settings/SettingsPage.js +++ b/src/renderer/src/stories/pages/settings/SettingsPage.js @@ -20,8 +20,6 @@ const schema = merge( } ); -console.log(schema); - import { Button } from "../../Button.js"; import { global } from "../../../progress/index.js"; import { merge, setUndefinedIfNotDeclared } from "../utils.js"; From cefafc37d811120eb22588e84f9a2c6e1719a1ee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:31:48 +0000 Subject: [PATCH 022/133] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../src/stories/pages/guided-mode/data/GuidedMetadata.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 13e876b5f..45c366beb 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -18,7 +18,7 @@ import { Button } from "../../../Button.js"; import globalIcon from "../../../assets/global.svg?raw"; -const imagingPlaneKey = 'imaging_plane'; +const imagingPlaneKey = "imaging_plane"; const propsToIgnore = { Ophys: { ImagingPlane: { From 95e3b5fc374adb6355491dab68f0afc0964c7e2b Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Wed, 29 Nov 2023 16:34:48 -0800 Subject: [PATCH 023/133] Basic patternProperties rendering --- src/renderer/src/stories/JSONSchemaForm.js | 47 ++- src/renderer/src/stories/JSONSchemaInput.js | 357 +++++++++++------- src/renderer/src/stories/List.ts | 33 +- src/renderer/src/stories/SimpleTable.js | 3 +- .../pages/guided-mode/data/GuidedMetadata.js | 6 +- .../guided-mode/data/GuidedPathExpansion.js | 1 - .../src/stories/pages/inspect/InspectPage.js | 2 +- .../src/stories/pages/preview/PreviewPage.js | 2 +- .../src/stories/pages/uploads/UploadsPage.js | 2 +- 9 files changed, 285 insertions(+), 168 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index aa6bef129..13c827803 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -451,7 +451,7 @@ export class JSONSchemaForm extends LitElement { // path = path.slice(this.base.length); // Correct for base path if (!path) throw new Error("Path not specified"); return path.reduce( - (acc, curr) => (acc = acc?.[curr] ?? acc?.[omitted.find((str) => acc[str])]?.[curr]), + (acc, curr) => (acc = acc?.[curr] ?? acc?.[omitted.find((str) => acc[str]?.[curr])]?.[curr]), object ); }; @@ -473,20 +473,20 @@ export class JSONSchemaForm extends LitElement { if (indexOf !== -1) path = path.slice(indexOf + 1); } - const resolved = this.#get(path, schema, ["properties"]); + const resolved = this.#get(path, schema, ["properties", "patternProperties"]); if (resolved["$ref"]) return this.getSchema(resolved["$ref"].split("/").slice(1)); // NOTE: This assumes reference to the root of the schema return resolved; } - #renderInteractiveElement = (name, info, required, path = []) => { + #renderInteractiveElement = (name, info, required, path = [], value) => { let isRequired = required[name]; const localPath = [...path, name]; const externalPath = [...this.base, ...localPath]; const resolved = this.#get(path, this.resolved); - const value = resolved[name]; + if (value === undefined) value = resolved[name]; const isConditional = this.#getLink(externalPath) || typeof isRequired === "function"; // Check the two possible ways of determining if a field is conditional @@ -504,12 +504,12 @@ export class JSONSchemaForm extends LitElement { }; const interactiveInput = new JSONSchemaInput({ - info, + schema: info, path: localPath, value, form: this, required: isRequired, - validateEmptyValue: this.validateEmptyValues, + validateEmptyValue: this.validateEmptyValues }); // this.validateEmptyValues ? undefined : (el) => (el.value ?? el.checked) !== "" @@ -530,7 +530,7 @@ export class JSONSchemaForm extends LitElement { ? "conditional" : ""}" > - + ${interactiveInput}
@@ -832,13 +832,15 @@ export class JSONSchemaForm extends LitElement { #render = (schema, results, required = {}, ignore = {}, path = []) => { let isLink = Symbol("isLink"); + + const hasPatternProperties = !!schema.patternProperties; + // Filter non-required properties (if specified) and render the sub-schema const renderable = this.#getRenderable(schema, required, ignore, path); - // // Filter non-required properties (if specified) and render the sub-schema // const renderable = path.length ? this.#getRenderable(schema, required) : Object.entries(schema.properties ?? {}) - if (renderable.length === 0) return html`
${this.emptyMessage}
`; + if (renderable.length === 0 && !hasPatternProperties) return html`
${this.emptyMessage}
`; let renderableWithLinks = renderable.reduce((acc, [name, info]) => { const externalPath = [...this.base, ...path, name]; @@ -896,9 +898,12 @@ export class JSONSchemaForm extends LitElement { const finalSort = this.sort ? sorted.sort(this.sort) : sorted; + let rendered = finalSort.map((entry) => { const [name, info] = entry; + const hasPatternProperties = !!info.patternProperties; + // Render linked properties if (entry[isLink]) { const linkedProperties = info.properties.map((path) => { @@ -960,9 +965,10 @@ export class JSONSchemaForm extends LitElement { enableToggle.checked = !isDisabled; const nestedResults = __disabled[name] ?? results[name] ?? this.results[name]; // One or the other will exist—depending on global or local disabling - + if (renderableInside.length) { - this.#nestedForms[name] = new JSONSchemaForm({ + const ogContext = this + const nested = this.#nestedForms[name] = new JSONSchemaForm({ identifier: this.identifier, schema: info, results: { ...nestedResults }, @@ -993,7 +999,9 @@ export class JSONSchemaForm extends LitElement { this.nLoaded++; this.checkAllLoaded(); }, - createTable: (...args) => this.createTable(...args), + createTable: function (...args) { + return ogContext.createTable.call(this, ...args) + }, onOverride: (...args) => this.onOverride(...args), base, }); @@ -1006,7 +1014,7 @@ export class JSONSchemaForm extends LitElement { toggleable: hasMany, subtitle: html`
${explicitlyRequired ? "" : enableToggleContainer}${renderableInside.length - ? `${renderableInside.length} fields` + ? `${hasPatternProperties ? 'Dynamic' : renderableInside.length} fields` : ""}
`, content: this.#nestedForms[name], @@ -1086,6 +1094,19 @@ export class JSONSchemaForm extends LitElement { return accordion; }); + if (hasPatternProperties) { + + const patternProps = Object.entries(schema.patternProperties).map(( [ key, schema ] ) => { + return this.#renderInteractiveElement(key, schema, required, path, results) + }) + + return [ + ...rendered, + ...patternProps + ] + } + + return rendered; }; diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 69a8242ef..cf20fc690 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -96,10 +96,11 @@ export class JSONSchemaInput extends LitElement { `; } - // info, + // schema, // parent, // path, // form, + // patternProperties required = false; validateOnChange = true; @@ -116,12 +117,13 @@ export class JSONSchemaInput extends LitElement { // Update the actual input element const el = this.getElement(); if (el.type === "checkbox") el.checked = value; - else if (el.classList.contains("list")) - el.children[0].items = value - ? value.map((value) => { - return { value }; - }) - : []; + else if (el.classList.contains("list")) { + const list = el.children[0]; + el.children[0].items = this.#mapToList({ + value, + list + }) // NOTE: Make sure this is correct + } else if (el instanceof Search) el.shadowRoot.querySelector("input").value = value; else el.value = value; } @@ -136,7 +138,7 @@ export class JSONSchemaInput extends LitElement { return true; } - getElement = () => this.shadowRoot.querySelector(".schema-input"); + getElement = () => this.shadowRoot.querySelector(".schema-input") #activateTimeoutValidation = (name, path) => { this.#clearTimeoutValidation(); @@ -174,35 +176,176 @@ export class JSONSchemaInput extends LitElement { } render() { - const { info } = this; + const { schema } = this; const input = this.#render(); return html` ${input}

- ${info.description - ? html`${unsafeHTML(capitalize(info.description))}${info.description.slice(-1)[0] === "." - ? "" - : "."}` - : ""} + ${schema.description + ? html`${unsafeHTML(capitalize(schema.description))}${schema.description.slice(-1)[0] === "." + ? "" + : "."}` + : ""}

`; } #onThrow = (...args) => (this.onThrow ? this.onThrow(...args) : this.form?.onThrow(...args)); + #isEditableObject = (schema = this.schema) => schema.type === "object"; + + + #list + #mapToList({ + value = this.value, + schema = this.schema, + list + } = {}) { + + const { path: fullPath } = this; + const path = typeof fullPath === "string" ? fullPath.split("-") : [...fullPath]; + const name = path.splice(-1)[0]; + + const isEditableObject = this.#isEditableObject() + const resolved = isEditableObject ? Object.values(value ?? {}) : value ?? []; + let items = resolved ? resolved.map((value) => { return { value } }) : [] + + + if (isEditableObject) { + + let regex; + try { regex = new RegExp(name) } catch (e) { } + + items = Object.entries(this.value) + + if (regex) items = items.filter(([key]) => regex.test(key)) + + items = items.map(([key, value]) => { + return { + key, + value, + controls: [ + new Button({ + label: 'Edit', + size: "small", + onClick: () => { + + this.#createModal({ + key, + schema, + results: value, + list: list ?? this.#list + }) + } + }) + ] + } + }) + } + + return items + } + + #schemaElement + #modal + + async #createModal({ + key = name, + schema, + results, + list + } = {}) { + + const createNewObject = !results + + if (this.#modal) this.#modal.remove(); + + const submitButton = new Button({ + label: "Submit", + primary: true, + }); + + const updateTarget = results ?? {}; + + submitButton.addEventListener("click", async () => { + + if (this.#schemaElement instanceof JSONSchemaForm) await this.#schemaElement.validate(); + + let value = updateTarget + + if (schema?.format && schema.properties) { + let newValue = schema?.format; + for (let key in schema.properties) newValue = newValue.replace(`{${key}}`, value[key] ?? "").trim(); + value = newValue; + } + + if (createNewObject) list.add({ value }); + else list.requestUpdate(); + + this.#modal.toggle(false); + }); + + this.#modal = new Modal({ + header: header(key), + footer: submitButton, + showCloseButton: createNewObject + }); + + const div = document.createElement("div"); + div.style.padding = "25px"; + + console.warn('Creating form', name, results, schema, name) + + const isObject = schema.type === "object" || schema.properties; // NOTE: For formatted strings, this is not an object + + this.#schemaElement = isObject + ? new JSONSchemaForm({ + schema, + results: updateTarget, + onUpdate: (internalPath, value, forceUpdate) => { + if (createNewObject) updateTarget[key] = value + else { + const path = [key, ...internalPath]; + console.log(path, this.path) + this.#updateData(path, value, forceUpdate); + } + }, + onThrow: this.#onThrow, + }) + : new JSONSchemaInput({ + schema, + validateOnChange: false, + path: this.path, + form: this.form, + value: updateTarget, + onUpdate: (value) => { + if (createNewObject) updateTarget[key] = value + else this.#updateData(key, value, forceUpdate); // NOTE: Untested + }, + }); + + div.append(this.#schemaElement); + + this.#modal.append(div); + + document.body.append(this.#modal); + + setTimeout(() => this.#modal.toggle(true)); + } + #render() { - const { validateOnChange, info, path: fullPath } = this; + const { validateOnChange, schema, path: fullPath } = this; const path = typeof fullPath === "string" ? fullPath.split("-") : [...fullPath]; const name = path.splice(-1)[0]; - const isArray = info.type === "array"; // Handle string (and related) formats / types + const isArray = schema.type === "array"; // Handle string (and related) formats / types - const hasItemsRef = "items" in info && "$ref" in info.items; - if (!("items" in info)) info.items = {}; - if (!("type" in info.items) && !hasItemsRef) info.items.type = "string"; + const hasItemsRef = "items" in schema && "$ref" in schema.items; + if (!("items" in schema)) schema.items = {}; + if (!("type" in schema.items) && !hasItemsRef) schema.items.type = "string"; // Handle file and directory formats const createFilesystemSelector = (format) => { @@ -220,10 +363,10 @@ export class JSONSchemaInput extends LitElement { return el; }; - if (isArray) { + if (isArray || this.#isEditableObject()) { // if ('value' in this && !Array.isArray(this.value)) this.value = [ this.value ] - const itemSchema = this.form ? this.form.getSchema("items", info) : info["items"]; + const itemSchema = this.form ? this.form.getSchema("items", schema) : schema["items"]; const fileSystemFormat = isFilesystemSelector(name, itemSchema.format); if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat); @@ -248,8 +391,8 @@ export class JSONSchemaInput extends LitElement { (this.onValidate ? this.onValidate() : this.form - ? this.form.validateOnChange(key, parent, [...this.form.base, ...fullPath], v) - : "") + ? this.form.validateOnChange(key, parent, [...this.form.base, ...fullPath], v) + : "") ); }, @@ -270,94 +413,24 @@ export class JSONSchemaInput extends LitElement { if (table) return (this.form.tables[name] = table === true ? new BasicTable(tableMetadata) : table); } - const headerText = document.createElement("span"); - headerText.innerText = header(name); - const addButton = new Button({ size: "small", }); addButton.innerText = "Add Item"; - let modal; - - let tempParent = {}; - - const itemInfo = info.items; - const formProperties = itemInfo.properties; - - let element; - addButton.addEventListener("click", () => { - if (modal) modal.remove(); - - tempParent[name] = {}; // Wipe previous values - - modal = new Modal({ - header: headerText, - footer: submitButton, - }); - - const div = document.createElement("div"); - div.style.padding = "25px"; - - element = formProperties - ? new JSONSchemaForm({ - schema: itemInfo, - results: tempParent[name], - onThrow: this.#onThrow, - }) - : new JSONSchemaInput({ - info: itemInfo, - validateOnChange: false, - path: this.path, - form: this.form, - onUpdate: (value) => (tempParent[name] = value), - }); - - div.append(element); - - modal.append(div); - - addButton.insertAdjacentElement("beforebegin", modal); - - setTimeout(() => modal.toggle(true)); + this.#createModal({ list, schema: itemSchema }); }); - const list = new List({ - items: this.value - ? this.value.map((value) => { - return { value }; - }) - : [], + const list = this.#list = new List({ + items: this.#mapToList(), onChange: async () => { this.#updateData(fullPath, list.items.length ? list.items.map((o) => o.value) : undefined); if (validateOnChange) await this.#triggerValidation(name, path); }, }); - const submitButton = new Button({ - label: "Submit", - primary: true, - }); - - submitButton.addEventListener("click", async () => { - let value = tempParent[name]; - - if (formProperties) { - await element.validate(); - if (itemInfo?.format) { - let newValue = itemInfo?.format; - for (let key in formProperties) - newValue = newValue.replace(`{${key}}`, value[key] ?? "").trim(); - value = newValue; - } - } - - list.add({ value }); - modal.toggle(false); - }); - return html`
validateOnChange && this.#triggerValidation(name, path)}> ${list} ${addButton} @@ -366,14 +439,14 @@ export class JSONSchemaInput extends LitElement { } // Basic enumeration of properties on a select element - if (info.enum && info.enum.length) { - if (info.strict === false) { + if (schema.enum && schema.enum.length) { + if (schema.strict === false) { // const category = categories.find(({ test }) => test.test(key))?.value; - const options = info.enum.map((v) => { + const options = schema.enum.map((v) => { return { key: v, - keywords: info.enumKeywords?.[v], + keywords: schema.enumKeywords?.[v], }; }); @@ -396,19 +469,19 @@ export class JSONSchemaInput extends LitElement { return html` `; - } else if (info.type === "boolean") { + } else if (schema.type === "boolean") { return html` validateOnChange && this.#triggerValidation(name, path)} />`; - } else if (info.type === "string" || info.type === "number" || info.type === "integer") { - const isInteger = info.type === "integer"; - if (isInteger) info.type = "number"; - const isNumber = info.type === "number"; + } else if (schema.type === "string" || schema.type === "number" || schema.type === "integer") { + const isInteger = schema.type === "integer"; + if (isInteger) schema.type = "number"; + const isNumber = schema.type === "number"; - const fileSystemFormat = isFilesystemSelector(name, info.format); + const fileSystemFormat = isFilesystemSelector(name, schema.format); if (fileSystemFormat) return createFilesystemSelector(fileSystemFormat); // Handle long string formats - else if (info.format === "long" || isArray) + else if (schema.format === "long" || isArray) return html`