From 0cde27deeac9dc18db1334cebc9bf4b27c4e1640 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 6 May 2024 13:58:55 -0700 Subject: [PATCH 01/15] Validate correctly and hide subject metadata from global popup --- src/renderer/src/stories/JSONSchemaInput.js | 2 +- src/renderer/src/stories/forms/GlobalFormModal.ts | 5 ++--- .../src/stories/pages/guided-mode/data/GuidedMetadata.js | 5 ++++- src/renderer/src/validation/validation.json | 2 ++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 736eaa78b..8f2be1571 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -1001,7 +1001,7 @@ export class JSONSchemaInput extends LitElement { if (ev.key === "Enter") submitButton.onClick(); }); - return html`
+ return html`
validateOnChange && this.#triggerValidation(name, path)}>
${input}${submitButton}
${list}
`; diff --git a/src/renderer/src/stories/forms/GlobalFormModal.ts b/src/renderer/src/stories/forms/GlobalFormModal.ts index 8329efa05..ccfbf55e5 100644 --- a/src/renderer/src/stories/forms/GlobalFormModal.ts +++ b/src/renderer/src/stories/forms/GlobalFormModal.ts @@ -62,12 +62,11 @@ export function createFormModal ({ Object.entries(props).forEach(([key, value]) => { if (key === '*') return if (value === true) delete obj[key] - else removeProperties(obj[key], value, globals) + else if (key in obj) removeProperties(obj[key], value, globals) }) } - if (hasInstances) Object.keys(schemaCopy.properties).forEach(key => removeProperties(schemaCopy.properties[key].properties, propsToRemove[key], propsToRemove["*"])) - else removeProperties(schemaCopy.properties, propsToRemove) + removeProperties(schemaCopy.properties, propsToRemove) const globalForm = new JSONSchemaForm({ validateEmptyValues: null, 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 174676443..415ba681b 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -196,9 +196,12 @@ export class GuidedMetadataPage extends ManagedPage { // Provide HARDCODED global schema for metadata properties (not automatically abstracting across sessions)... const schema = preprocessMetadataSchema(undefined, true); + const toRemove = structuredClone(propsToIgnore); + toRemove.Subject = true // Do not edit subject defaults + const modal = (this.#globalModal = createGlobalFormModal.call(this, { header: "Global Metadata", - propsToRemove: propsToIgnore, + propsToRemove: toRemove, schema, hasInstances: true, mergeFunction: function (globalResolved, globals) { diff --git a/src/renderer/src/validation/validation.json b/src/renderer/src/validation/validation.json index 420ea3887..8e7b3dd8d 100644 --- a/src/renderer/src/validation/validation.json +++ b/src/renderer/src/validation/validation.json @@ -6,6 +6,8 @@ "NWBFile": { "description": "check_description", "experiment_description": "check_experiment_description", + "institution": "check_institution", + "keywords": "check_keywords", "identifier": false, "session_description": false, "lab": false, From 7c745a7055c95d6cd7c9d50d24239af94f19c2e3 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 6 May 2024 15:03:37 -0700 Subject: [PATCH 02/15] Add units to inputs and put descriptions above tables --- src/renderer/src/stories/BasicTable.js | 10 +++--- src/renderer/src/stories/JSONSchemaInput.js | 36 ++++++++++++++------- src/renderer/src/stories/SimpleTable.js | 6 ++-- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/renderer/src/stories/BasicTable.js b/src/renderer/src/stories/BasicTable.js index c4cef137a..d46598107 100644 --- a/src/renderer/src/stories/BasicTable.js +++ b/src/renderer/src/stories/BasicTable.js @@ -476,6 +476,11 @@ export class BasicTable extends LitElement { const description = this.#schema.description; return html` + ${description + ? html`

+ ${description} +

` + : ""}
@@ -540,11 +545,6 @@ export class BasicTable extends LitElement { ${this.truncated ? html`

...

` : ""} - ${description - ? html`

- ${description} -

` - : ""} `; } } diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 8f2be1571..5a6f9105c 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -366,6 +366,7 @@ export class JSONSchemaInput extends LitElement { main { display: flex; + align-items: center; } #controls { @@ -457,6 +458,10 @@ export class JSONSchemaInput extends LitElement { font-size: 1.2em !important; } + :host([data-table]) .guided--form-label { + margin-bottom: 0px; + } + .guided--form-label.centered { text-align: center; } @@ -647,6 +652,15 @@ export class JSONSchemaInput extends LitElement { const description = this.description ?? schema.description; + + const descriptionHTML = description ? html`

+ ${unsafeHTML(capitalize(description))}${[".", "?", "!"].includes(description.slice(-1)[0]) + ? "" + : "."} +

` + : "" + + return html`
` : "" } -
${input}${this.controls ? html`
${this.controls}
` : ""}
- ${ - description - ? html`

- ${unsafeHTML(capitalize(description))}${[".", "?", "!"].includes(description.slice(-1)[0]) - ? "" - : "."} -

` - : "" - } + ${descriptionHTML}
`; } @@ -862,6 +867,8 @@ export class JSONSchemaInput extends LitElement { #render() { const { validateOnChange, schema, path: fullPath } = this; + this.removeAttribute('data-table') + // Do your best to fill in missing schema values if (!("type" in schema)) schema.type = this.#getType(); @@ -1024,7 +1031,13 @@ export class JSONSchemaInput extends LitElement { onThrow: this.#onThrow, }); // Ensure change propagates - if (table) return table; + if (table) { + this.setAttribute('data-table', '') + setTimeout(() => { + console.log(this) + }) + return table; + } } const addButton = new Button({ @@ -1231,6 +1244,7 @@ export class JSONSchemaInput extends LitElement { @change=${(ev) => validateOnChange && this.#triggerValidation(name, path)} @keydown=${this.#moveToNextInput} /> + ${schema.unit ?? ''} ${isRequiredNumber ? html`
+ Right click to add or remove rows. +

@@ -998,9 +1001,6 @@ export class SimpleTable extends LitElement {
-

- Right click to add or remove rows. -

`; } } From 2b3974ae6495f408fd181a5d2e980d1c6194b541 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 6 May 2024 15:05:24 -0700 Subject: [PATCH 03/15] Update base_metadata_schema.json --- schemas/json/base_metadata_schema.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/schemas/json/base_metadata_schema.json b/schemas/json/base_metadata_schema.json index 2e694d499..184712417 100644 --- a/schemas/json/base_metadata_schema.json +++ b/schemas/json/base_metadata_schema.json @@ -11,11 +11,10 @@ "required": ["session_start_time"], "properties": { "keywords": { - "title": "Keyword", "description": "Terms to search over", "type": "array", "items": { - "title": "keyword", + "title": "Keyword", "type": "string" } }, From ea268e178a6538c25346e697a2bc41a4efa5607a Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 6 May 2024 15:15:32 -0700 Subject: [PATCH 04/15] Update list of globals --- src/renderer/src/stories/JSONSchemaInput.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 5a6f9105c..2fbde2477 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -575,14 +575,11 @@ export class JSONSchemaInput extends LitElement { const inputElement = this.getElement(); if (!inputElement) return false; + const hasList = inputElement.querySelector('nwb-list') + if (inputElement.type === "checkbox") inputElement.checked = value; - else if (inputElement.classList.contains("list")) { - const list = inputElement.children[0]; - inputElement.children[0].items = this.#mapToList({ - value, - list, - }); // NOTE: Make sure this is correct - } else if (inputElement instanceof Search) inputElement.shadowRoot.querySelector("input").value = value; + else if (hasList) hasList.items = this.#mapToList({ value, hasList }); // NOTE: Make sure this is correct + else if (inputElement instanceof Search) inputElement.shadowRoot.querySelector("input").value = value; else inputElement.value = value; } @@ -1033,9 +1030,6 @@ export class JSONSchemaInput extends LitElement { if (table) { this.setAttribute('data-table', '') - setTimeout(() => { - console.log(this) - }) return table; } } From d58f7359d8cdf2c2e59fb7a39f05598e9ce78965 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 6 May 2024 15:37:01 -0700 Subject: [PATCH 05/15] Limit date selector for subjects --- src/renderer/src/stories/hot.js | 22 +++++++++++++++++++ .../src/stories/table/cells/date-time.ts | 4 ---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/stories/hot.js b/src/renderer/src/stories/hot.js index 83a077862..483178f1e 100644 --- a/src/renderer/src/stories/hot.js +++ b/src/renderer/src/stories/hot.js @@ -22,6 +22,25 @@ function arrayRenderer(instance, td, row, col, prop, value, cellProperties) { return td; } + +// Borrowed from https://stackoverflow.com/a/29774197/7290573 +function getDate(days) { + let date; + + if (days !== undefined) { + date = new Date(Date.now() + days * 24 * 60 * 60 * 1000); + } else { + date = new Date(); + } + + const offset = date.getTimezoneOffset(); + + date = new Date(date.getTime() - (offset*60*1000)); + + return date.toISOString(); +} + + class DateTimeEditor extends Handsontable.editors.BaseEditor { constructor(hotInstance) { super(hotInstance); @@ -36,6 +55,9 @@ class DateTimeEditor extends Handsontable.editors.BaseEditor { this.DATETIME.style.display = "none"; this.DATETIME.input.style.width = "0px"; // Don't actually show the input, just the picker + this.DATETIME.input.min = "1900-01-01T00:00" + this.DATETIME.input.max = getDate(0).slice(0, -2) + // Attach node to DOM, by appending it to the container holding the table this.hot.rootElement.appendChild(this.DATETIME); } diff --git a/src/renderer/src/stories/table/cells/date-time.ts b/src/renderer/src/stories/table/cells/date-time.ts index 8d1922576..5d0202af0 100644 --- a/src/renderer/src/stories/table/cells/date-time.ts +++ b/src/renderer/src/stories/table/cells/date-time.ts @@ -1,10 +1,8 @@ -import { LitElement } from 'lit'; import { DateTimeSelector } from '../../DateTimeSelector' import { TableCellBase } from "./base"; import { BaseRenderer } from './renderers/base'; - export class DateTimeCell extends TableCellBase { constructor() { @@ -79,8 +77,6 @@ export class DateTimeEditor extends BaseRenderer { this.DATETIME.type = "datetime-local"; this.DATETIME.style.position = "absolute"; this.DATETIME.style.display = "none"; - // this.DATETIME.input.style.width = "0px"; // Don't actually show the input, just the picker - // this.DATETIME.value = this.value } render() { From 8758760aaa088009ec745bc4338b64b67316384b Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 6 May 2024 16:12:52 -0700 Subject: [PATCH 06/15] Show suffixes for source data inputs --- schemas/interfaces.info.ts | 25 ++++++++++ schemas/source-data.schema.ts | 18 ++++++- .../guided-mode/data/GuidedSourceData.js | 2 +- .../pages/guided-mode/data/GuidedStructure.js | 48 +++++++++---------- .../pages/guided-mode/setup/GuidedSubjects.js | 8 +++- 5 files changed, 73 insertions(+), 28 deletions(-) create mode 100644 schemas/interfaces.info.ts diff --git a/schemas/interfaces.info.ts b/schemas/interfaces.info.ts new file mode 100644 index 000000000..62317f8a9 --- /dev/null +++ b/schemas/interfaces.info.ts @@ -0,0 +1,25 @@ +import { baseUrl, onServerOpen } from '../src/renderer/src/server/globals' +import { isStorybook } from '../src/renderer/src/dependencies/simple' + +const values = { interfaces: {} } +const setReady: any = {} + +const createPromise = (prop: string) => new Promise((resolve) => setReady[prop] = (value) => { + values[prop] = value + resolve(value) +}) + +export const ready = { + interfaces: createPromise("interfaces"), +} + +// Get CPUs +onServerOpen(async () => { + await fetch(`${baseUrl}/neuroconv`).then((res) => res.json()) + .then((interfaces) => setReady.interfaces(interfaces)) + .catch(() => { + if (isStorybook) setReady.interfaces({}) + }); +}); + +export default values \ No newline at end of file diff --git a/schemas/source-data.schema.ts b/schemas/source-data.schema.ts index 2c5ac3bbe..ef189b856 100644 --- a/schemas/source-data.schema.ts +++ b/schemas/source-data.schema.ts @@ -1,10 +1,20 @@ -// import { merge } from "../src/renderer/src/stories/pages/utils" + +import interfaceInfo from './interfaces.info' export default function preprocessSourceDataSchema (schema) { + const interfaces = Object.values(interfaceInfo.interfaces).reduce((acc, { name, ...rest }) => { + acc[name] = rest + return acc + }, {}) + // Abstract across different interfaces Object.entries(schema.properties ?? {}).forEach(([key, schema]: [string, any]) => { + const info = interfaces[key] ?? {} + + const singleLocationInfo = schema.properties.file_path ?? schema.properties.folder_path + if (schema.properties.file_paths) { Object.assign(schema.properties.file_paths, { items: { type: 'string' }, @@ -13,6 +23,12 @@ export default function preprocessSourceDataSchema (schema) { }) } + else if (singleLocationInfo) { + + if (!singleLocationInfo.description && info.suffixes) singleLocationInfo.description = `Suffixes: ${info.suffixes.join(', ')}` + + } + // Do not show steps if (schema.properties.gain) schema.properties.gain.step = null 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 12f5a3818..8acdea3ae 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -163,7 +163,7 @@ export class GuidedSourceDataPage extends ManagedPage { const instanceId = `sub-${subject}/ses-${session}`; - const schema = this.info.globalState.schema.source_data; + const schema = structuredClone(this.info.globalState.schema.source_data); delete schema.description; const form = new JSONSchemaForm({ diff --git a/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js b/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js index deb5ddc6d..0edab3205 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js @@ -8,6 +8,7 @@ import { Search } from "../../../Search.js"; import { Modal } from "../../../Modal"; import { List } from "../../../List"; import { baseUrl } from "../../../../server/globals"; +import { ready } from "../../../../../../../schemas/interfaces.info"; const defaultEmptyMessage = "No formats selected"; @@ -116,31 +117,28 @@ export class GuidedStructurePage extends Page { this.list.emptyMessage = "Loading valid formats..."; - this.search.options = await fetch(`${baseUrl}/neuroconv`) - .then((res) => res.json()) - .then((json) => - Object.entries(json).map(([key, value]) => { - const displayName = key.trim(); - - const interfaceName = value.name; - - const category = categories.find(({ test }) => test.test(interfaceName))?.value; - - const structuredKeywords = { - suffixes: value.suffixes ?? [], - }; - - return { - ...value, // Contains label and name already (extra metadata) - key: displayName, - value: interfaceName, - structuredKeywords, - category, - disabled: !supportedInterfaces.includes(interfaceName), - }; - }) - ) - .catch((error) => console.error(error)); + const interfaceInfo = await ready.interfaces + + this.search.options = Object.entries(interfaceInfo).map(([key, value]) => { + const displayName = key.trim(); + + const interfaceName = value.name; + + const category = categories.find(({ test }) => test.test(interfaceName))?.value; + + const structuredKeywords = { + suffixes: value.suffixes ?? [], + }; + + return { + ...value, // Contains label and name already (extra metadata) + key: displayName, + value: interfaceName, + structuredKeywords, + category, + disabled: !supportedInterfaces.includes(interfaceName), + }; + }) this.list.emptyMessage = defaultEmptyMessage; diff --git a/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js b/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js index e4eb5491f..54f4e8056 100644 --- a/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js +++ b/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js @@ -85,7 +85,13 @@ export class GuidedSubjectsPage extends Page { updateResultsFromSubjects(results, subjects, sourceDataObject, nameMap); // NOTE: This directly mutates the results object }; - footer = {}; + footer = { + onNext: () => { + const extraElements = document.querySelectorAll('.HandsontableCopyPaste') + extraElements.forEach((element) => element.remove()); + return this.to(1); + } + }; updated() { super.updated(); // Call if updating data From 82c9607e3edc628c0119834245ec5948c99b8906 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 6 May 2024 16:14:31 -0700 Subject: [PATCH 07/15] Tighter searching behavior --- src/renderer/src/stories/Search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/stories/Search.js b/src/renderer/src/stories/Search.js index 3b6c844bf..ddf87e403 100644 --- a/src/renderer/src/stories/Search.js +++ b/src/renderer/src/stories/Search.js @@ -167,7 +167,7 @@ export class Search extends LitElement { } .option { - padding: 25px; + padding: 10px 18px; border-top: 1px solid #f2f2f2; } From 70f1f3874f951981c904f9d443c2c022d2c8db2b Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 6 May 2024 16:20:38 -0700 Subject: [PATCH 08/15] Show editing in place slightly better --- src/renderer/src/stories/List.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/src/stories/List.ts b/src/renderer/src/stories/List.ts index 558bbbc56..5fe983e47 100644 --- a/src/renderer/src/stories/List.ts +++ b/src/renderer/src/stories/List.ts @@ -99,6 +99,10 @@ export class List extends LitElement { cursor: text; } + [contenteditable="true"]:hover { + background-color: rgba(217, 245, 255, 0.6); + } + [data-idx]{ background: #f0f0f0; height: 25px; From 354ae29f19a0d635b3298e5e0153e551f5f3358b Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 6 May 2024 16:30:14 -0700 Subject: [PATCH 09/15] Allow using esc. Show warning if no files found --- src/renderer/src/stories/Modal.ts | 20 +++++++++++++++++++ .../guided-mode/data/GuidedPathExpansion.js | 11 ++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/stories/Modal.ts b/src/renderer/src/stories/Modal.ts index e9832e92c..d2a4d84a3 100644 --- a/src/renderer/src/stories/Modal.ts +++ b/src/renderer/src/stories/Modal.ts @@ -183,8 +183,28 @@ export class Modal extends LitElement { } + // Use escape key to close modal + #handleKeyDown = (ev: KeyboardEvent) => { + if (ev.key === 'Escape') { + const selected = document.activeElement + if (selected instanceof HTMLElement) selected.blur() + this.toggle(false) + } + } + + connectedCallback() { + super.connectedCallback(); + document.addEventListener('keydown', this.#handleKeyDown) + } + + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener('keydown', this.#handleKeyDown) + } + render() { + return html` ${isMultipleTypes ? html`
From ac94bb6d08127fd8a8409ef810f870df02e64751 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 20:18:04 +0000 Subject: [PATCH 11/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- schemas/interfaces.info.ts | 2 +- src/renderer/src/stories/BasicTable.js | 4 +- .../src/stories/FileSystemSelector.js | 4 +- src/renderer/src/stories/JSONSchemaInput.js | 50 +++++++++---------- src/renderer/src/stories/hot.js | 8 ++- .../pages/guided-mode/data/GuidedMetadata.js | 4 +- .../guided-mode/data/GuidedPathExpansion.js | 13 ++--- .../pages/guided-mode/data/GuidedStructure.js | 4 +- .../pages/guided-mode/setup/GuidedSubjects.js | 4 +- 9 files changed, 44 insertions(+), 49 deletions(-) diff --git a/schemas/interfaces.info.ts b/schemas/interfaces.info.ts index 62317f8a9..40fac2253 100644 --- a/schemas/interfaces.info.ts +++ b/schemas/interfaces.info.ts @@ -22,4 +22,4 @@ onServerOpen(async () => { }); }); -export default values \ No newline at end of file +export default values diff --git a/src/renderer/src/stories/BasicTable.js b/src/renderer/src/stories/BasicTable.js index d46598107..9b7f61d5a 100644 --- a/src/renderer/src/stories/BasicTable.js +++ b/src/renderer/src/stories/BasicTable.js @@ -478,8 +478,8 @@ export class BasicTable extends LitElement { return html` ${description ? html`

- ${description} -

` + ${description} +

` : ""}
diff --git a/src/renderer/src/stories/FileSystemSelector.js b/src/renderer/src/stories/FileSystemSelector.js index 5563ce57c..7a38d1f5e 100644 --- a/src/renderer/src/stories/FileSystemSelector.js +++ b/src/renderer/src/stories/FileSystemSelector.js @@ -7,7 +7,6 @@ const { dialog } = remote; import restartSVG from "../stories/assets/restart.svg?raw"; import { unsafeSVG } from "lit/directives/unsafe-svg.js"; - function getObjectTypeReferenceString(type, multiple, { nested, native } = {}) { if (Array.isArray(type)) return `${multiple ? "" : "a "}${type @@ -180,7 +179,7 @@ export class FilesystemSelector extends LitElement { if (dialog) { const results = await this.#useElectronDialog(type); // const path = file.filePath ?? file.filePaths?.[0]; - const resolved = results.filePath ?? results.filePaths + const resolved = results.filePath ?? results.filePaths; if (!resolved) return; // Cancelled this.#handleFiles(results.filePath ?? results.filePaths, type); } else { @@ -273,7 +272,6 @@ export class FilesystemSelector extends LitElement {
${this.value ? html`
this.#handleFiles()}>${unsafeSVG(restartSVG)}
` : ""}
- ${isMultipleTypes ? html`
diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 2fbde2477..828f5ba96 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -575,10 +575,11 @@ export class JSONSchemaInput extends LitElement { const inputElement = this.getElement(); if (!inputElement) return false; - const hasList = inputElement.querySelector('nwb-list') + const hasList = inputElement.querySelector("nwb-list"); if (inputElement.type === "checkbox") inputElement.checked = value; - else if (hasList) hasList.items = this.#mapToList({ value, hasList }); // NOTE: Make sure this is correct + else if (hasList) + hasList.items = this.#mapToList({ value, hasList }); // NOTE: Make sure this is correct else if (inputElement instanceof Search) inputElement.shadowRoot.querySelector("input").value = value; else inputElement.value = value; } @@ -649,28 +650,21 @@ export class JSONSchemaInput extends LitElement { const description = this.description ?? schema.description; - - const descriptionHTML = description ? html`

- ${unsafeHTML(capitalize(description))}${[".", "?", "!"].includes(description.slice(-1)[0]) - ? "" - : "."} -

` - : "" - + const descriptionHTML = description + ? html`

+ ${unsafeHTML(capitalize(description))}${[".", "?", "!"].includes(description.slice(-1)[0]) ? "" : "."} +

` + : ""; return html` -
- - ${ - this.showLabel - ? html`` - : "" - } +
+ ${this.showLabel + ? html`` + : ""}
${input}${this.controls ? html`
${this.controls}
` : ""}
${descriptionHTML}
@@ -864,7 +858,7 @@ export class JSONSchemaInput extends LitElement { #render() { const { validateOnChange, schema, path: fullPath } = this; - this.removeAttribute('data-table') + this.removeAttribute("data-table"); // Do your best to fill in missing schema values if (!("type" in schema)) schema.type = this.#getType(); @@ -1005,7 +999,11 @@ export class JSONSchemaInput extends LitElement { if (ev.key === "Enter") submitButton.onClick(); }); - return html`
validateOnChange && this.#triggerValidation(name, path)}> + return html`
validateOnChange && this.#triggerValidation(name, path)} + >
${input}${submitButton}
${list}
`; @@ -1029,7 +1027,7 @@ export class JSONSchemaInput extends LitElement { }); // Ensure change propagates if (table) { - this.setAttribute('data-table', '') + this.setAttribute("data-table", ""); return table; } } @@ -1238,7 +1236,7 @@ export class JSONSchemaInput extends LitElement { @change=${(ev) => validateOnChange && this.#triggerValidation(name, path)} @keydown=${this.#moveToNextInput} /> - ${schema.unit ?? ''} + ${schema.unit ?? ""} ${isRequiredNumber ? html`
{ const displayName = key.trim(); @@ -138,7 +138,7 @@ export class GuidedStructurePage extends Page { category, disabled: !supportedInterfaces.includes(interfaceName), }; - }) + }); this.list.emptyMessage = defaultEmptyMessage; diff --git a/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js b/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js index 5d611fb3d..2bce759ff 100644 --- a/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js +++ b/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js @@ -87,10 +87,10 @@ export class GuidedSubjectsPage extends Page { footer = { onNext: () => { - const extraElements = document.querySelectorAll('.HandsontableCopyPaste') + const extraElements = document.querySelectorAll(".HandsontableCopyPaste"); extraElements.forEach((element) => element.remove()); return this.to(1); - } + }, }; updated() { From 7f34ea1c0cc2da9685a28a6fda0b9adb6485fa2e Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 13 May 2024 13:24:06 -0700 Subject: [PATCH 12/15] Limit subject DOB in File Metadata page too --- schemas/base-metadata.schema.ts | 11 +++++++++++ src/renderer/src/stories/DateTimeSelector.js | 7 ++++++- src/renderer/src/stories/JSONSchemaInput.js | 6 ++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 419ca5a40..4decf0b19 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -40,6 +40,15 @@ function getSpeciesInfo(species: any[][] = []) { } + +// Borrowed from https://stackoverflow.com/a/29774197/7290573 +function getCurrentDate() { + const date = new Date() + const offset = date.getTimezoneOffset(); + return (new Date(date.getTime() - (offset*60*1000))).toISOString(); +} + + function updateEcephysTable(propName, schema, schemaToMerge) { const ecephys = schema.properties.Ecephys @@ -112,6 +121,8 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, globa strict: false, description: 'The species of your subject.' } + subjectProps.date_of_birth.minimum = "1900-01-01T00:00" + subjectProps.date_of_birth.maximum = getCurrentDate().slice(0, -2) // copy.order = ['NWBFile', 'Subject'] diff --git a/src/renderer/src/stories/DateTimeSelector.js b/src/renderer/src/stories/DateTimeSelector.js index e9e56b779..c81b6ecf6 100644 --- a/src/renderer/src/stories/DateTimeSelector.js +++ b/src/renderer/src/stories/DateTimeSelector.js @@ -32,10 +32,15 @@ export class DateTimeSelector extends LitElement { } } - constructor({ value } = {}) { + constructor({ value, schema } = {}) { super(); this.input = document.createElement("input"); this.input.type = "datetime-local"; + if (schema) { + const { min, max } = schema; + if (min) this.input.min = min; + if (max) this.input.max = max; + } this.addEventListener("click", () => { this.input.focus(); diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 2fbde2477..129b562ed 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -1192,6 +1192,10 @@ export class JSONSchemaInput extends LitElement { const value = isDateTime ? resolveDateTime(this.value) : this.value; + const { minimum, maximum, exclusiveMax, exclusiveMin } = schema; + const min = exclusiveMin ?? minimum; + const max = exclusiveMax ?? maximum; + return html` { let value = ev.target.value; let newValue = value; From 50f6d4eef0a01999732d5b1c8093e668b32c78a2 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 13 May 2024 13:29:20 -0700 Subject: [PATCH 13/15] Use the schema for the subject table --- src/renderer/src/stories/Table.js | 2 ++ src/renderer/src/stories/hot.js | 24 ++++-------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/renderer/src/stories/Table.js b/src/renderer/src/stories/Table.js index e1a7e1713..bdec1c9ba 100644 --- a/src/renderer/src/stories/Table.js +++ b/src/renderer/src/stories/Table.js @@ -303,6 +303,8 @@ export class Table extends LitElement { if (colInfo.format === "date-time") { info.type = "date-time"; info.correctFormat = false; + info.min = colInfo.exclusiveMinimum ?? colInfo.minimum; + info.max = colInfo.exclusiveMaximum ?? colInfo.maximum; } if (colInfo.type === "array") { diff --git a/src/renderer/src/stories/hot.js b/src/renderer/src/stories/hot.js index 87a265bd3..31d332bd0 100644 --- a/src/renderer/src/stories/hot.js +++ b/src/renderer/src/stories/hot.js @@ -22,23 +22,6 @@ function arrayRenderer(instance, td, row, col, prop, value, cellProperties) { return td; } -// Borrowed from https://stackoverflow.com/a/29774197/7290573 -function getDate(days) { - let date; - - if (days !== undefined) { - date = new Date(Date.now() + days * 24 * 60 * 60 * 1000); - } else { - date = new Date(); - } - - const offset = date.getTimezoneOffset(); - - date = new Date(date.getTime() - offset * 60 * 1000); - - return date.toISOString(); -} - class DateTimeEditor extends Handsontable.editors.BaseEditor { constructor(hotInstance) { super(hotInstance); @@ -53,9 +36,6 @@ class DateTimeEditor extends Handsontable.editors.BaseEditor { this.DATETIME.style.display = "none"; this.DATETIME.input.style.width = "0px"; // Don't actually show the input, just the picker - this.DATETIME.input.min = "1900-01-01T00:00"; - this.DATETIME.input.max = getDate(0).slice(0, -2); - // Attach node to DOM, by appending it to the container holding the table this.hot.rootElement.appendChild(this.DATETIME); } @@ -79,6 +59,10 @@ class DateTimeEditor extends Handsontable.editors.BaseEditor { style[this.hot.isRtl() ? "right" : "left"] = `${start}px`; style.margin = "0px"; style.display = ""; + + this.DATETIME.input.min = this.cellProperties.min + this.DATETIME.input.max = this.cellProperties.max + } focus() { From b30d9ca8a8a7b3ab3edef72eeeedab272d6daac4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 20:29:38 +0000 Subject: [PATCH 14/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/hot.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/stories/hot.js b/src/renderer/src/stories/hot.js index 31d332bd0..b17534cf0 100644 --- a/src/renderer/src/stories/hot.js +++ b/src/renderer/src/stories/hot.js @@ -60,9 +60,8 @@ class DateTimeEditor extends Handsontable.editors.BaseEditor { style.margin = "0px"; style.display = ""; - this.DATETIME.input.min = this.cellProperties.min - this.DATETIME.input.max = this.cellProperties.max - + this.DATETIME.input.min = this.cellProperties.min; + this.DATETIME.input.max = this.cellProperties.max; } focus() { From 96f202618775fa26ce72e81e8681d9a8e7df1a06 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 14 May 2024 11:56:32 -0700 Subject: [PATCH 15/15] Update SimpleTable.js --- src/renderer/src/stories/SimpleTable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/stories/SimpleTable.js b/src/renderer/src/stories/SimpleTable.js index fbc4d4c17..bd89de2d7 100644 --- a/src/renderer/src/stories/SimpleTable.js +++ b/src/renderer/src/stories/SimpleTable.js @@ -991,12 +991,12 @@ export class SimpleTable extends LitElement { if (editOptions.row?.add) rowEditOptions.push("add"); if (editOptions.row?.remove) rowEditOptions.push("remove"); - const description = rowEditOptions.length ? `Right click to ${rowEditOptions.join(" or ")} rows.` : ""; + const genericDescription = rowEditOptions.length ? `Right click to ${rowEditOptions.join(" or ")} rows.` : ""; return html` ${this.#context}

- Right click to add or remove rows. + ${genericDescription}