From f2d0a8af6a9dc7452496b0492158d2cde125a51d Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Sun, 12 Nov 2023 09:05:13 -0500 Subject: [PATCH 01/18] Move species enumeration (non-strict) into the base --- schemas/base-metadata.schema.ts | 28 +++++++++++++++++++-- schemas/subject.schema.ts | 24 +----------------- src/renderer/src/stories/JSONSchemaInput.js | 4 ++- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 372979e22..b2c40e9e8 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -3,15 +3,39 @@ import baseMetadataSchema from './json/base_metadata_schema.json' assert { type: export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { // Add unit to weight - schema.properties.Subject.properties.weight.unit = 'kg' + const subjectProps = schema.properties.Subject.properties + subjectProps.weight.unit = 'kg' - schema.properties.Subject.properties.sex.enumLabels = { + subjectProps.sex.enumLabels = { M: 'Male', F: 'Female', U: 'Unknown', O: 'Other' } + const species = [ + "Mus musculus - House mouse", + "Homo sapiens - Human", + "Rattus norvegicus - Norway rat", + "Rattus rattus - Black rat", + "Macaca mulatta - Rhesus monkey", + "Callithrix jacchus - Common marmoset", + "Drosophila melanogaster - Fruit fly", + "Danio rerio - Zebra fish", + "Caenorhabditis elegans" + ].map(str => str.split(' - ')[0]) // Remove common names so this passes the validator + + subjectProps.species = { + type: 'string', + // enumLabels: + enum: species, // Remove common names so this passes the validator + items: { + type: 'string' + }, + strict: false, + description: 'The species of your subject.' + } + // Ensure experimenter schema has custom structure schema.properties.NWBFile.properties.experimenter = baseMetadataSchema.properties.NWBFile.properties.experimenter diff --git a/schemas/subject.schema.ts b/schemas/subject.schema.ts index 2aa8b0f26..5ea2aaeaa 100644 --- a/schemas/subject.schema.ts +++ b/schemas/subject.schema.ts @@ -1,4 +1,4 @@ -import nwbBaseSchema from './base-metadata.schema' +import nwbBaseSchema from './base-metadata.schema' const removeSubset = (data, subset) => { const subsetData = subset.reduce((acc, key) => { acc[key] = data[key]; return acc }, {}) @@ -6,28 +6,6 @@ const removeSubset = (data, subset) => { return subsetData } - const species = [ - "Mus musculus - House mouse", - "Homo sapiens - Human", - "Rattus norvegicus - Norway rat", - "Rattus rattus - Black rat", - "Macaca mulatta - Rhesus monkey", - "Callithrix jacchus - Common marmoset", - "Drosophila melanogaster - Fruit fly", - "Danio rerio - Zebra fish", - "Caenorhabditis elegans" - ].map(str => str.split(' - ')[0]) // Remove common names so this passes the validator - -nwbBaseSchema.properties.Subject.properties.species = { - type: 'string', - enum: species, - items: { - type: 'string' - }, - strict: false, - description: 'The species of your subject.' -} - // Sort the subject schema const ageGroupKeys = ['age', 'age__reference', 'date_of_birth'] const genotypeGroupKeys = ['genotype', 'strain'] diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 4ad9a8742..652ef5602 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -353,7 +353,8 @@ export class JSONSchemaInput extends LitElement { } // Basic enumeration of properties on a select element - if (info.enum) { + if (info.enum && info.strict !== false) { + return html` Date: Wed, 15 Nov 2023 10:09:52 -0500 Subject: [PATCH 02/18] Basic dropdown using the search component --- schemas/base-metadata.schema.ts | 14 +- src/renderer/src/stories/JSONSchemaInput.js | 28 ++- src/renderer/src/stories/Search.js | 169 ++++++++++++------ .../pages/guided-mode/data/GuidedStructure.js | 3 + 4 files changed, 158 insertions(+), 56 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index b2c40e9e8..2c98c79ee 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -22,13 +22,19 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { "Callithrix jacchus - Common marmoset", "Drosophila melanogaster - Fruit fly", "Danio rerio - Zebra fish", - "Caenorhabditis elegans" - ].map(str => str.split(' - ')[0]) // Remove common names so this passes the validator + "Caenorhabditis elegans - Roundworm" + ] // Remove common names so this passes the validator subjectProps.species = { type: 'string', - // enumLabels: - enum: species, // Remove common names so this passes the validator + + enumLabels: species.reduce((acc, v) => { + const [ k, label ] = v.split(' - ') + acc[k] = label + return acc + }, {}), + + enum: species.map(str => str.split(' - ')[0]), // Remove common names so this passes the validator items: { type: 'string' }, diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 652ef5602..f960a98e3 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -11,6 +11,7 @@ import { Modal } from "./Modal"; import { capitalize } from "./forms/utils"; import { JSONSchemaForm } from "./JSONSchemaForm"; +import { Search } from "./Search"; const isFilesystemSelector = (name, format) => { if (Array.isArray(format)) return format.map((f) => isFilesystemSelector(name, f)).every(Boolean) ? format : null; @@ -353,7 +354,32 @@ export class JSONSchemaInput extends LitElement { } // Basic enumeration of properties on a select element - if (info.enum && info.strict !== false) { + if (info.enum) { + + if (info.strict === false) { + // const category = categories.find(({ test }) => test.test(key))?.value; + + const options = info.enum.map((v) => { + return { + key: v, + keywords: [ info.enumLabels?.[v] ] ?? [] + // label: info.label, + // value: info.value, + // category, + }; // Has label and keywords property already + }) + + + return new Search({ + options, + value: this.value, + showAllWhenEmpty: false, + listMode: 'click', + onChange: (value) => this.#updateData(fullPath, value), + onThrow: (...args) => this.#onThrow(...args), + }) + } + return html` { +
+ { + ev.stopPropagation(); + if (this.listMode === 'click') { + const input = ev.target.value; + this.#populate(input) + } + }} @input=${(ev) => { const input = ev.target.value; - - // Hide all if empty - if (!input) { - this.#initialize(); - return; - } - - const toShow = []; - // Check if the input value matches the label - this.#options.forEach(({ option, label }, i) => { - if (label.toLowerCase().includes(input.toLowerCase()) && !toShow.includes(i)) toShow.push(i); - }); - - // Check if the input value matches any of the keywords - this.#options.forEach(({ option, keywords }, i) => { - keywords.forEach((keyword) => { - if (keyword.toLowerCase().includes(input.toLowerCase()) && !toShow.includes(i)) toShow.push(i); - }); - }); - - this.#options.forEach(({ option }, i) => { - if (toShow.includes(i)) { - option.removeAttribute("hidden"); - } else { - option.setAttribute("hidden", ""); - } - }); - - categories.forEach(({ entries, element }) => { - if (entries.reduce((acc, el) => acc + el.hasAttribute("hidden"), 0) === entries.length) - element.setAttribute("hidden", ""); - else element.removeAttribute("hidden"); - }); + this.#populate(input) }}>
${this.list} 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 3fc80b22b..610d2b09c 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js @@ -46,6 +46,9 @@ export class GuidedStructurePage extends Page { search = new Search({ disabledLabel: "Not supported", + headerStyles: { + padding: '15px' + } }); list = new List({ From ad3e700b94612e4f242a7ae33d40e89e7061d186 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:18:56 +0000 Subject: [PATCH 03/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- schemas/base-metadata.schema.ts | 4 +- src/renderer/src/stories/JSONSchemaInput.js | 12 ++-- src/renderer/src/stories/Search.js | 63 +++++++------------ src/renderer/src/stories/Table.js | 3 +- .../pages/guided-mode/data/GuidedStructure.js | 4 +- .../pages/guided-mode/options/utils.js | 38 +++++------ 6 files changed, 49 insertions(+), 75 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index 2c98c79ee..facc6be32 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -24,10 +24,10 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { "Danio rerio - Zebra fish", "Caenorhabditis elegans - Roundworm" ] // Remove common names so this passes the validator - + subjectProps.species = { type: 'string', - + enumLabels: species.reduce((acc, v) => { const [ k, label ] = v.split(' - ') acc[k] = label diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index a17fe7c63..4a2e485ec 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -358,32 +358,29 @@ export class JSONSchemaInput extends LitElement { // Basic enumeration of properties on a select element if (info.enum) { - if (info.strict === false) { // const category = categories.find(({ test }) => test.test(key))?.value; const options = info.enum.map((v) => { return { key: v, - keywords: [ info.enumLabels?.[v] ] ?? [] + keywords: [info.enumLabels?.[v]] ?? [], // label: info.label, // value: info.value, // category, }; // Has label and keywords property already - }) - + }); return new Search({ options, value: this.value, showAllWhenEmpty: false, - listMode: 'click', + listMode: "click", onChange: (value) => this.#updateData(fullPath, value), onThrow: (...args) => this.#onThrow(...args), - }) + }); } - return html` { - if (this.listMode === 'click') this.setAttribute('active', false) - }) + document.addEventListener("click", () => { + if (this.listMode === "click") this.setAttribute("active", false); + }); } static get styles() { @@ -62,7 +54,6 @@ export class Search extends LitElement { opacity: 0.5; } - ul { list-style: none; padding: 0; @@ -81,7 +72,7 @@ export class Search extends LitElement { max-height: 50vh; } - :host([active=false]) ul{ + :host([active="false"]) ul { visibility: hidden; } @@ -133,7 +124,6 @@ export class Search extends LitElement { white-space: nowrap; min-width: min-content; } - `; } @@ -148,14 +138,14 @@ export class Search extends LitElement { updated() { const options = this.shadowRoot.querySelectorAll(".option"); this.#options = Array.from(options).map((option) => { - const keywordString = option.getAttribute("data-keywords") + const keywordString = option.getAttribute("data-keywords"); const keywords = keywordString ? JSON.parse(keywordString) : []; return { option, keywords, label: option.querySelector(".label").innerText }; }); this.#initialize(); - console.log(this) + console.log(this); } onSelect = (id, value) => {}; @@ -172,20 +162,18 @@ export class Search extends LitElement { option[this.showAllWhenEmpty ? "removeAttribute" : "setAttribute"]("hidden", "") ); - if (!this.showAllWhenEmpty) this.setAttribute('active', false) - } + if (!this.showAllWhenEmpty) this.setAttribute("active", false); + }; list = document.createElement("ul"); categories = {}; - #sortedCategories = [] - + #sortedCategories = []; - #populate = ( input ) => { - - // Hide all if empty - if (!input) { + #populate = (input) => { + // Hide all if empty + if (!input) { this.#initialize(); return; } @@ -211,7 +199,6 @@ export class Search extends LitElement { option.setAttribute("hidden", ""); } }); - this.#sortedCategories.forEach(({ entries, element }) => { if (entries.reduce((acc, el) => acc + el.hasAttribute("hidden"), 0) === entries.length) @@ -219,10 +206,8 @@ export class Search extends LitElement { else element.removeAttribute("hidden"); }); - - this.setAttribute("active", !!toShow.length) - - } + this.setAttribute("active", !!toShow.length); + }; render() { this.categories = {}; @@ -319,11 +304,11 @@ export class Search extends LitElement { } // Categories sorted alphabetically - const categories = this.#sortedCategories = Object.values(this.categories).sort((a, b) => { + const categories = (this.#sortedCategories = Object.values(this.categories).sort((a, b) => { if (a.element.innerText < b.element.innerText) return -1; if (a.element.innerText > b.element.innerText) return 1; return 0; - }); + })); categories.forEach(({ entries, element }) => { this.list.append(element, ...entries); @@ -331,17 +316,17 @@ export class Search extends LitElement { return html`
{ - ev.stopPropagation(); - if (this.listMode === 'click') { - const input = ev.target.value; - this.#populate(input) - } + ev.stopPropagation(); + if (this.listMode === "click") { + const input = ev.target.value; + this.#populate(input); + } }} @input=${(ev) => { const input = ev.target.value; - this.#populate(input) + this.#populate(input); }}>
${this.list} diff --git a/src/renderer/src/stories/Table.js b/src/renderer/src/stories/Table.js index 3037eba08..9612ac6b6 100644 --- a/src/renderer/src/stories/Table.js +++ b/src/renderer/src/stories/Table.js @@ -432,9 +432,8 @@ export class Table extends LitElement { const initialCellsToUpdate = data.reduce((acc, v) => acc + v.length, 0); table.addHook("afterValidate", (isValid, value, row, prop) => { - const isUserUpdate = initialCellsToUpdate <= validated; - + if (isUserUpdate) { const header = typeof prop === "number" ? colHeaders[prop] : prop; let rowName = this.keyColumn ? rowHeaders[row] : row; 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 610d2b09c..1856e119f 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js @@ -47,8 +47,8 @@ export class GuidedStructurePage extends Page { search = new Search({ disabledLabel: "Not supported", headerStyles: { - padding: '15px' - } + padding: "15px", + }, }); list = new List({ 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 7057f83e0..68781e8ab 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/utils.js +++ b/src/renderer/src/stories/pages/guided-mode/options/utils.js @@ -24,9 +24,8 @@ export const openProgressSwal = (options, callback) => { export const run = async (url, payload, options = {}) => { const needsSwal = !options.swal && options.swal !== false; - - if (needsSwal) { + if (needsSwal) { if (!("showCancelButton" in options)) { options.showCancelButton = true; options.customClass = { actions: "swal-conversion-actions" }; @@ -36,37 +35,32 @@ export const run = async (url, payload, options = {}) => { options.fetch = { signal: cancelController.signal, - } + }; const popup = await openProgressSwal(options, (result) => { if (!result.isConfirmed) cancelController.abort(); }).then(async (swal) => { - if (options.onOpen) await options.onOpen(swal) - return swal + if (options.onOpen) await options.onOpen(swal); + return swal; }); const element = popup.getHtmlContainer(); - const actions = popup.getActions() - const loader = actions.querySelector(".swal2-loader") + const actions = popup.getActions(); + const loader = actions.querySelector(".swal2-loader"); const container = document.createElement("div"); - container.append(loader) + container.append(loader); element.innerText = ""; Object.assign(container.style, { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - marginBottom: '25px' - }) + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + marginBottom: "25px", + }); element.appendChild(container); - element.insertAdjacentHTML( - "beforeend", - `
` - ); - - + element.insertAdjacentHTML("beforeend", `
`); } if (!("base" in options)) options.base = "/neuroconv"; @@ -79,8 +73,8 @@ export const run = async (url, payload, options = {}) => { headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), ...(options.fetch ?? {}), - }).then((res) => res.json()) - + }).then((res) => res.json()); + if (needsSwal) Swal.close(); if (results?.message) throw new Error(`Request to ${url} failed: ${results.message}`); From a3ba23fd51bb4c378bc551c31f898ae3ff486a24 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Wed, 15 Nov 2023 13:39:26 -0500 Subject: [PATCH 04/18] Make search and table behave consistently. Add icon --- pyflask/app.py | 5 ++ schemas/base-metadata.schema.ts | 62 ++++++++++------ schemas/subject.schema.ts | 26 ++++--- src/renderer/src/globals.js | 2 +- src/renderer/src/server.ts | 37 ++++++++++ src/renderer/src/stories/JSONSchemaForm.js | 1 - src/renderer/src/stories/JSONSchemaInput.js | 29 ++++---- src/renderer/src/stories/Search.js | 70 ++++++++++++++++--- src/renderer/src/stories/Table.stories.js | 4 +- src/renderer/src/stories/assets/search.svg | 1 + src/renderer/src/stories/forms/utils.ts | 4 +- .../pages/guided-mode/data/GuidedMetadata.js | 5 +- .../pages/guided-mode/data/GuidedStructure.js | 4 +- .../options/GuidedInspectorPage.js | 2 - .../guided-mode/setup/GuidedNewDatasetInfo.js | 4 +- .../pages/guided-mode/setup/GuidedSubjects.js | 8 +-- 16 files changed, 193 insertions(+), 71 deletions(-) create mode 100644 src/renderer/src/stories/assets/search.svg diff --git a/pyflask/app.py b/pyflask/app.py index 977dae0f3..5b5e591f1 100644 --- a/pyflask/app.py +++ b/pyflask/app.py @@ -104,6 +104,11 @@ def get_cpu_count(): return dict(physical=physical, logical=logical) +@app.route("/get-recommended-species") +def get_species(): + from dandi.metadata import species_map + return species_map + @api.route("/server_shutdown", endpoint="shutdown") class Shutdown(Resource): diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index facc6be32..dd5340660 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -1,11 +1,45 @@ +import { serverGlobals, resolve } from '../src/renderer/src/server' +import { header } from '../src/renderer/src/stories/forms/utils' import baseMetadataSchema from './json/base_metadata_schema.json' assert { type: "json" } +function getSpeciesNameComponents(arr: any[]) { + const split = arr[arr.length - 1].split(' - ') + return { + name: split[0], + label: split[1] + } +} + + +function getSpeciesInfo(species: any[][] = []) { + + + return { + + enumLabels: species.reduce((acc, arr) => { + acc[getSpeciesNameComponents(arr).name] = arr[arr.length - 1] + return acc + }, {}), + + enumKeywords: species.reduce((acc, arr) => { + const info = getSpeciesNameComponents(arr) + acc[info.name] = info.label ? [`${header(info.label)} — ${arr[0].join(', ')}`] : arr[0] + return acc + }, {}), + + enum: species.map(arr => getSpeciesNameComponents(arr).name), // Remove common names so this passes the validator + } + +} + export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { // Add unit to weight const subjectProps = schema.properties.Subject.properties subjectProps.weight.unit = 'kg' + // subjectProps.order = ['weight', 'age', 'age__reference', 'date_of_birth', 'genotype', 'strain'] + subjectProps.sex.enumLabels = { M: 'Male', F: 'Female', @@ -13,28 +47,10 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { O: 'Other' } - const species = [ - "Mus musculus - House mouse", - "Homo sapiens - Human", - "Rattus norvegicus - Norway rat", - "Rattus rattus - Black rat", - "Macaca mulatta - Rhesus monkey", - "Callithrix jacchus - Common marmoset", - "Drosophila melanogaster - Fruit fly", - "Danio rerio - Zebra fish", - "Caenorhabditis elegans - Roundworm" - ] // Remove common names so this passes the validator - + subjectProps.species = { type: 'string', - - enumLabels: species.reduce((acc, v) => { - const [ k, label ] = v.split(' - ') - acc[k] = label - return acc - }, {}), - - enum: species.map(str => str.split(' - ')[0]), // Remove common names so this passes the validator + ...getSpeciesInfo(), items: { type: 'string' }, @@ -42,6 +58,12 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { description: 'The species of your subject.' } + // Resolve species suggestions + resolve(serverGlobals.species, (res) => { + const info = getSpeciesInfo(res) + Object.assign(subjectProps.species, info) + }) + // Ensure experimenter schema has custom structure schema.properties.NWBFile.properties.experimenter = baseMetadataSchema.properties.NWBFile.properties.experimenter diff --git a/schemas/subject.schema.ts b/schemas/subject.schema.ts index 5ea2aaeaa..36c2db437 100644 --- a/schemas/subject.schema.ts +++ b/schemas/subject.schema.ts @@ -1,4 +1,4 @@ -import nwbBaseSchema from './base-metadata.schema' +import { preprocessMetadataSchema } from './base-metadata.schema' const removeSubset = (data, subset) => { const subsetData = subset.reduce((acc, key) => { acc[key] = data[key]; return acc }, {}) @@ -6,17 +6,22 @@ const removeSubset = (data, subset) => { return subsetData } -// Sort the subject schema -const ageGroupKeys = ['age', 'age__reference', 'date_of_birth'] -const genotypeGroupKeys = ['genotype', 'strain'] -const groups = [...ageGroupKeys, ...genotypeGroupKeys] -const standardOrder = {...nwbBaseSchema.properties.Subject.properties} -const group = removeSubset(standardOrder, groups) -const required = removeSubset(standardOrder, nwbBaseSchema.properties.Subject.required) +export default (schema) => { -let newRequiredArray = [...nwbBaseSchema.properties.Subject.required, 'sessions'] + const nwbBaseSchema = preprocessMetadataSchema(schema) -export default { + // Sort the subject schema + const ageGroupKeys = ['age', 'age__reference', 'date_of_birth'] + const genotypeGroupKeys = ['genotype', 'strain'] + const groups = [...ageGroupKeys, ...genotypeGroupKeys] + const standardOrder = {...nwbBaseSchema.properties.Subject.properties} + const group = removeSubset(standardOrder, groups) + const required = removeSubset(standardOrder, nwbBaseSchema.properties.Subject.required) + + let newRequiredArray = [...nwbBaseSchema.properties.Subject.required, 'sessions'] + + + return { ...nwbBaseSchema.properties.Subject, properties: { sessions: { @@ -31,3 +36,4 @@ export default { }, required: newRequiredArray } +} diff --git a/src/renderer/src/globals.js b/src/renderer/src/globals.js index b235577dd..89dc5af42 100644 --- a/src/renderer/src/globals.js +++ b/src/renderer/src/globals.js @@ -12,4 +12,4 @@ export let runOnLoad = (fn) => { // Base Request URL for Python Server export const baseUrl = `http://127.0.0.1:${port}`; -export const supportedInterfaces = guideGlobalMetadata.supported_interfaces; +export const supportedInterfaces = guideGlobalMetadata.supported_interfaces; \ No newline at end of file diff --git a/src/renderer/src/server.ts b/src/renderer/src/server.ts index d0add2853..e04bf290e 100644 --- a/src/renderer/src/server.ts +++ b/src/renderer/src/server.ts @@ -140,3 +140,40 @@ export const loadServerEvents = () => { else activateServer() // Just mock-activate the server if we're in the browser } + + + +const isPromise = (o) => typeof o === 'object' && typeof o.then === 'function' + +export const resolve = (object, callback) => { + if (isPromise(object)) { + return new Promise(resolvePromise => { + object.then((res) => resolvePromise((callback) ? callback(res) : res)) + }) + } else return (callback) ? callback(object) : object +} + +export const serverGlobals = { + species: new Promise((res, rej) => { + onServerOpen(() => { + fetch(new URL("get-recommended-species", baseUrl)) + .then((res) => res.json()) + .then((species) => { + res(species) + serverGlobals.species = species + }) + .catch(() => rej()); + }); + }), + cpus: new Promise((res, rej) => { + onServerOpen(() => { + fetch(new URL("cpus", baseUrl)) + .then((res) => res.json()) + .then((cpus) => { + res(cpus) + serverGlobals.cpus = cpus + }) + .catch(() => rej()); + }); + }) +} \ No newline at end of file diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index 259f0644f..ef68b52a3 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -387,7 +387,6 @@ export class JSONSchemaForm extends LitElement { if (flaggedInputs.length) { flaggedInputs[0].focus(); if (!message) { - console.log(flaggedInputs); if (flaggedInputs.length === 1) message = `${header(flaggedInputs[0].path.join("."))} is not valid`; else message = `${flaggedInputs.length} invalid form values`; diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 4a2e485ec..1326e6fcc 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -357,28 +357,33 @@ export class JSONSchemaInput extends LitElement { } // Basic enumeration of properties on a select element - if (info.enum) { + if (info.enum && info.enum.length) { + if (info.strict === false) { // const category = categories.find(({ test }) => test.test(key))?.value; const options = info.enum.map((v) => { return { key: v, - keywords: [info.enumLabels?.[v]] ?? [], - // label: info.label, - // value: info.value, - // category, - }; // Has label and keywords property already - }); + keywords: info.enumKeywords?.[v] + }; + }) - return new Search({ + + const search = new Search({ options, value: this.value, showAllWhenEmpty: false, - listMode: "click", - onChange: (value) => this.#updateData(fullPath, value), - onThrow: (...args) => this.#onThrow(...args), - }); + listMode: 'click', + onSelect: async ({ value, key }) => { + const result = value ?? key + this.#updateData(fullPath, result) + if (validateOnChange) await this.#triggerValidation(name, path) + } + }) + + // search.classList.add("schema-input") + return search } return html` diff --git a/src/renderer/src/stories/Search.js b/src/renderer/src/stories/Search.js index e43aa865e..1b604dc47 100644 --- a/src/renderer/src/stories/Search.js +++ b/src/renderer/src/stories/Search.js @@ -1,20 +1,37 @@ import { LitElement, html, css } from "lit"; import { styleMap } from "lit/directives/style-map.js"; +import searchSVG from './assets/search.svg?raw'; + import tippy from "tippy.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; export class Search extends LitElement { - constructor({ options, showAllWhenEmpty = true, listMode = "list", headerStyles = {}, disabledLabel } = {}) { + constructor({ + value, + options, + showAllWhenEmpty = true, + listMode = "list", + headerStyles = {}, + disabledLabel, + onSelect + } = {} + ) { super(); + this.value = value; this.options = options; this.showAllWhenEmpty = showAllWhenEmpty; this.disabledLabel = disabledLabel; this.listMode = listMode; this.headerStyles = headerStyles; + if (onSelect) this.onSelect = onSelect - document.addEventListener("click", () => { - if (this.listMode === "click") this.setAttribute("active", false); - }); + document.addEventListener('click', () => { + if (this.listMode === 'click' && this.getAttribute('active') === 'true') { + this.#onSelect({ value: this.shadowRoot.querySelector('input').value }) + this.setAttribute('active', false) + } + }) } static get styles() { @@ -35,6 +52,7 @@ export class Search extends LitElement { .header { background: white; + position: relative; } input { @@ -50,6 +68,7 @@ export class Search extends LitElement { background-color: rgb(255, 255, 255); } + input::placeholder { opacity: 0.5; } @@ -70,6 +89,31 @@ export class Search extends LitElement { left: 0; right: 0; max-height: 50vh; + z-index: 2; + } + + svg { + position: absolute; + top: 50%; + right: 10px; + padding: 0px 15px; + transform: translateY(-50%); + fill: gray; + box-sizing: unset; + width: 20px; + height: 20px; + } + + :host([listmode="click"]) svg { + position: absolute; + top: 50%; + padding: 0px; + right: 10px; + transform: translateY(-50%); + fill: gray; + box-sizing: unset; + width: 20px; + height: 20px; } :host([active="false"]) ul { @@ -145,15 +189,22 @@ export class Search extends LitElement { this.#initialize(); - console.log(this); } onSelect = (id, value) => {}; - #onSelect = (id, value) => { - this.shadowRoot.querySelector("input").value = ""; + #onSelect = (option) => { + + const input = this.shadowRoot.querySelector("input"); + + if (this.listMode === 'click') { + input.value = option.value ?? option.key; + return this.onSelect(option); + } + + input.value = ""; this.#initialize(); - this.onSelect(id, value); + this.onSelect(option); }; #options = []; @@ -318,7 +369,7 @@ export class Search extends LitElement {
- { + { ev.stopPropagation(); if (this.listMode === "click") { const input = ev.target.value; @@ -328,6 +379,7 @@ export class Search extends LitElement { const input = ev.target.value; this.#populate(input); }}> + ${unsafeHTML(searchSVG)}
${this.list} `; diff --git a/src/renderer/src/stories/Table.stories.js b/src/renderer/src/stories/Table.stories.js index 47d38cdf6..93501dff0 100644 --- a/src/renderer/src/stories/Table.stories.js +++ b/src/renderer/src/stories/Table.stories.js @@ -1,6 +1,6 @@ import { Table } from "./Table.js"; -import subjectSchema from "../../../../schemas/subject.schema"; +import getSubjectSchema from "../../../../schemas/subject.schema"; import { SimpleTable } from "./SimpleTable.js"; import { BasicTable } from "./BasicTable.js"; @@ -26,6 +26,8 @@ const data = subjectIds.reduce((acc, key) => { const BasicTableTemplate = (args) => new BasicTable(args); +const subjectSchema = getSubjectSchema() + subjectSchema.additionalProperties = true; export const Basic = BasicTableTemplate.bind({}); diff --git a/src/renderer/src/stories/assets/search.svg b/src/renderer/src/stories/assets/search.svg new file mode 100644 index 000000000..f51c92487 --- /dev/null +++ b/src/renderer/src/stories/assets/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/src/stories/forms/utils.ts b/src/renderer/src/stories/forms/utils.ts index 64e8fad8c..ea3cdfb16 100644 --- a/src/renderer/src/stories/forms/utils.ts +++ b/src/renderer/src/stories/forms/utils.ts @@ -7,9 +7,7 @@ export const capitalize = (str: string) => { } -export const header = (headerStr: string) => { - return headerStr.split('_').filter(str => !!str).map(capitalize).join(' ') -} +export const header = (headerStr: string) => headerStr.split(/[_\s]/).filter(str => !!str).map(capitalize).join(' ') export const textToArray = (value: string) => value.split("\n") .map((str) => str.trim()) 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 ebfe5f8c9..f06be4cc5 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -15,7 +15,6 @@ import { header } from "../../../forms/utils"; import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; import { Button } from "../../../Button.js"; -import { globalSchema } from "../../../../../../../schemas/base-metadata.schema"; import globalIcon from "../../../assets/global.svg?raw"; @@ -86,7 +85,7 @@ export class GuidedMetadataPage extends ManagedPage { const modal = (this.#globalModal = createGlobalFormModal.call(this, { header: "Global Metadata", propsToRemove: [...propsToIgnore], - schema: globalSchema, // Provide HARDCODED global schema for metadata properties (not automatically abstracting across sessions)... + schema: preprocessMetadataSchema(), // Provide HARDCODED global schema for metadata properties (not automatically abstracting across sessions)... hasInstances: true, mergeFunction: function (globalResolved, globals) { merge(globalResolved, globals); @@ -144,8 +143,6 @@ export class GuidedMetadataPage extends ManagedPage { resolveResults(subject, session, globalState); - console.log(subject, session, results); - // Create the form const form = new JSONSchemaForm({ identifier: instanceId, 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 1856e119f..a365e2d5c 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js @@ -27,8 +27,8 @@ export class GuidedStructurePage extends Page { // Handle Search Bar Interactions this.search.list.style.position = "unset"; - this.search.onSelect = (...args) => { - this.list.add(...args); + this.search.onSelect = (item) => { + this.list.add(item); this.searchModal.toggle(false); }; diff --git a/src/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js b/src/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js index 8294691df..cd4dd6f45 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js +++ b/src/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js @@ -74,8 +74,6 @@ export class GuidedInspectorPage extends Page { const { globalState } = this.info; const { stubs, inspector } = globalState.preview; - console.log("Already got", inspector); - const opts = {}; // NOTE: Currently options are handled on the Python end until exposed to the user const title = "Inspecting your file"; diff --git a/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js b/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js index 6939693bb..f4f498fff 100644 --- a/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js +++ b/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js @@ -10,8 +10,8 @@ import { merge } from "../../utils.js"; import { schemaToPages } from "../../FormPage.js"; import { onThrow } from "../../../../errors"; -import { globalSchema } from "../../../../../../../schemas/base-metadata.schema"; import { header } from "../../../forms/utils"; +import { preprocessMetadataSchema } from "../../../../../../../schemas/base-metadata.schema"; const projectMetadataSchema = merge(projectGlobalSchema, projectGeneralSchema); @@ -86,7 +86,7 @@ export class GuidedNewDatasetPage extends Page { this.state = merge(global.data.output_locations, structuredClone(this.info.globalState.project)); - const pages = schemaToPages.call(this, globalSchema, ["project"], { validateEmptyValues: false }, (info) => { + const pages = schemaToPages.call(this, preprocessMetadataSchema(), ["project"], { validateEmptyValues: false }, (info) => { info.title = `${info.label} Global Metadata`; return info; }); 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 1078bd76b..b7b2f6622 100644 --- a/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js +++ b/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js @@ -1,12 +1,12 @@ import { html } from "lit"; import { Page } from "../../Page.js"; -import subjectSchema from "../../../../../../../schemas/subject.schema"; +import getSubjectSchema from "../../../../../../../schemas/subject.schema"; import { validateOnChange } from "../../../../validation/index.js"; import { Table } from "../../../Table.js"; import { updateResultsFromSubjects } from "./utils"; import { merge } from "../../utils.js"; -import { globalSchema } from "../../../../../../../schemas/base-metadata.schema"; +import { preprocessMetadataSchema } from "../../../../../../../schemas/base-metadata.schema"; import { Button } from "../../../Button.js"; import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; import { header } from "../../../forms/utils"; @@ -84,7 +84,7 @@ export class GuidedSubjectsPage extends Page { const modal = (this.#globalModal = createGlobalFormModal.call(this, { header: "Global Subject Metadata", key: "Subject", - schema: globalSchema.properties.Subject, + schema: preprocessMetadataSchema().properties.Subject, validateOnChange: (key, parent, path) => { return validateOnChange(key, parent, ["Subject", ...path]); }, @@ -112,7 +112,7 @@ export class GuidedSubjectsPage extends Page { } this.table = new Table({ - schema: subjectSchema, + schema: getSubjectSchema(), data: subjects, globals: this.info.globalState.project.Subject, keyColumn: "subject_id", From 395c20fcf3ac8a791e7ddf0c2de472c70fe24067 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:40:57 +0000 Subject: [PATCH 05/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyflask/app.py | 2 + schemas/base-metadata.schema.ts | 6 +-- src/renderer/src/globals.js | 2 +- src/renderer/src/server.ts | 2 +- src/renderer/src/stories/JSONSchemaInput.js | 20 +++++----- src/renderer/src/stories/Search.js | 38 +++++++++---------- src/renderer/src/stories/Table.stories.js | 2 +- src/renderer/src/stories/assets/search.svg | 2 +- .../guided-mode/setup/GuidedNewDatasetInfo.js | 14 +++++-- 9 files changed, 45 insertions(+), 43 deletions(-) diff --git a/pyflask/app.py b/pyflask/app.py index 5b5e591f1..ea666c0ce 100644 --- a/pyflask/app.py +++ b/pyflask/app.py @@ -104,9 +104,11 @@ def get_cpu_count(): return dict(physical=physical, logical=logical) + @app.route("/get-recommended-species") def get_species(): from dandi.metadata import species_map + return species_map diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index dd5340660..b406bdc46 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -12,7 +12,7 @@ function getSpeciesNameComponents(arr: any[]) { function getSpeciesInfo(species: any[][] = []) { - + return { @@ -28,7 +28,7 @@ function getSpeciesInfo(species: any[][] = []) { }, {}), enum: species.map(arr => getSpeciesNameComponents(arr).name), // Remove common names so this passes the validator - } + } } @@ -47,7 +47,7 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { O: 'Other' } - + subjectProps.species = { type: 'string', ...getSpeciesInfo(), diff --git a/src/renderer/src/globals.js b/src/renderer/src/globals.js index 89dc5af42..b235577dd 100644 --- a/src/renderer/src/globals.js +++ b/src/renderer/src/globals.js @@ -12,4 +12,4 @@ export let runOnLoad = (fn) => { // Base Request URL for Python Server export const baseUrl = `http://127.0.0.1:${port}`; -export const supportedInterfaces = guideGlobalMetadata.supported_interfaces; \ No newline at end of file +export const supportedInterfaces = guideGlobalMetadata.supported_interfaces; diff --git a/src/renderer/src/server.ts b/src/renderer/src/server.ts index e04bf290e..ad030e85c 100644 --- a/src/renderer/src/server.ts +++ b/src/renderer/src/server.ts @@ -176,4 +176,4 @@ export const serverGlobals = { .catch(() => rej()); }); }) -} \ No newline at end of file +} diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 1326e6fcc..fd96ce543 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -358,32 +358,30 @@ export class JSONSchemaInput extends LitElement { // Basic enumeration of properties on a select element if (info.enum && info.enum.length) { - if (info.strict === false) { // const category = categories.find(({ test }) => test.test(key))?.value; const options = info.enum.map((v) => { return { key: v, - keywords: info.enumKeywords?.[v] + keywords: info.enumKeywords?.[v], }; - }) - + }); const search = new Search({ options, value: this.value, showAllWhenEmpty: false, - listMode: 'click', + listMode: "click", onSelect: async ({ value, key }) => { - const result = value ?? key - this.#updateData(fullPath, result) - if (validateOnChange) await this.#triggerValidation(name, path) - } - }) + const result = value ?? key; + this.#updateData(fullPath, result); + if (validateOnChange) await this.#triggerValidation(name, path); + }, + }); // search.classList.add("schema-input") - return search + return search; } return html` diff --git a/src/renderer/src/stories/Search.js b/src/renderer/src/stories/Search.js index 1b604dc47..b692ee333 100644 --- a/src/renderer/src/stories/Search.js +++ b/src/renderer/src/stories/Search.js @@ -1,22 +1,21 @@ import { LitElement, html, css } from "lit"; import { styleMap } from "lit/directives/style-map.js"; -import searchSVG from './assets/search.svg?raw'; +import searchSVG from "./assets/search.svg?raw"; import tippy from "tippy.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; export class Search extends LitElement { - constructor({ - value, - options, - showAllWhenEmpty = true, - listMode = "list", - headerStyles = {}, - disabledLabel, - onSelect - } = {} - ) { + constructor({ + value, + options, + showAllWhenEmpty = true, + listMode = "list", + headerStyles = {}, + disabledLabel, + onSelect, + } = {}) { super(); this.value = value; this.options = options; @@ -24,14 +23,14 @@ export class Search extends LitElement { this.disabledLabel = disabledLabel; this.listMode = listMode; this.headerStyles = headerStyles; - if (onSelect) this.onSelect = onSelect + if (onSelect) this.onSelect = onSelect; - document.addEventListener('click', () => { - if (this.listMode === 'click' && this.getAttribute('active') === 'true') { - this.#onSelect({ value: this.shadowRoot.querySelector('input').value }) - this.setAttribute('active', false) + document.addEventListener("click", () => { + if (this.listMode === "click" && this.getAttribute("active") === "true") { + this.#onSelect({ value: this.shadowRoot.querySelector("input").value }); + this.setAttribute("active", false); } - }) + }); } static get styles() { @@ -68,7 +67,6 @@ export class Search extends LitElement { background-color: rgb(255, 255, 255); } - input::placeholder { opacity: 0.5; } @@ -188,16 +186,14 @@ export class Search extends LitElement { }); this.#initialize(); - } onSelect = (id, value) => {}; #onSelect = (option) => { - const input = this.shadowRoot.querySelector("input"); - if (this.listMode === 'click') { + if (this.listMode === "click") { input.value = option.value ?? option.key; return this.onSelect(option); } diff --git a/src/renderer/src/stories/Table.stories.js b/src/renderer/src/stories/Table.stories.js index 93501dff0..1d6225e40 100644 --- a/src/renderer/src/stories/Table.stories.js +++ b/src/renderer/src/stories/Table.stories.js @@ -26,7 +26,7 @@ const data = subjectIds.reduce((acc, key) => { const BasicTableTemplate = (args) => new BasicTable(args); -const subjectSchema = getSubjectSchema() +const subjectSchema = getSubjectSchema(); subjectSchema.additionalProperties = true; diff --git a/src/renderer/src/stories/assets/search.svg b/src/renderer/src/stories/assets/search.svg index f51c92487..c8519dc10 100644 --- a/src/renderer/src/stories/assets/search.svg +++ b/src/renderer/src/stories/assets/search.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js b/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js index f4f498fff..a206644b1 100644 --- a/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js +++ b/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js @@ -86,10 +86,16 @@ export class GuidedNewDatasetPage extends Page { this.state = merge(global.data.output_locations, structuredClone(this.info.globalState.project)); - const pages = schemaToPages.call(this, preprocessMetadataSchema(), ["project"], { validateEmptyValues: false }, (info) => { - info.title = `${info.label} Global Metadata`; - return info; - }); + const pages = schemaToPages.call( + this, + preprocessMetadataSchema(), + ["project"], + { validateEmptyValues: false }, + (info) => { + info.title = `${info.label} Global Metadata`; + return info; + } + ); pages.forEach((page) => { page.header = { From a754ee698ea6b135b50a3486d7e9308f3c530b0a Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Thu, 16 Nov 2023 10:28:04 -0800 Subject: [PATCH 06/18] Fix tests --- schemas/base-metadata.schema.ts | 4 +- src/renderer/src/globals.js | 5 +- src/renderer/src/index.ts | 2 +- src/renderer/src/server/globals.ts | 79 ++++++++++++++++++ .../src/{server.ts => server/index.ts} | 82 +------------------ .../guided-mode/data/GuidedSourceData.js | 4 +- .../pages/guided-mode/data/GuidedStructure.js | 3 +- .../pages/guided-mode/options/GuidedUpload.js | 4 +- .../pages/guided-mode/options/utils.js | 3 +- .../src/stories/pages/preview/PreviewPage.js | 2 +- .../src/stories/pages/uploads/UploadsPage.js | 4 +- src/renderer/src/stories/preview/Neurosift.js | 2 +- src/renderer/src/validation/index.js | 2 +- tests/metadata.test.ts | 4 + 14 files changed, 103 insertions(+), 97 deletions(-) create mode 100644 src/renderer/src/server/globals.ts rename src/renderer/src/{server.ts => server/index.ts} (57%) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index dd5340660..caae1219e 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -1,5 +1,7 @@ -import { serverGlobals, resolve } from '../src/renderer/src/server' +import { serverGlobals, resolve } from '../src/renderer/src/server/globals' + import { header } from '../src/renderer/src/stories/forms/utils' + import baseMetadataSchema from './json/base_metadata_schema.json' assert { type: "json" } function getSpeciesNameComponents(arr: any[]) { diff --git a/src/renderer/src/globals.js b/src/renderer/src/globals.js index 89dc5af42..c3d242768 100644 --- a/src/renderer/src/globals.js +++ b/src/renderer/src/globals.js @@ -1,4 +1,4 @@ -import { path, port } from "./electron/index.js"; +import { path } from "./electron/index.js"; import guideGlobalMetadata from "../../../guideGlobalMetadata.json" assert { type: "json" }; @@ -9,7 +9,4 @@ export let runOnLoad = (fn) => { else window.addEventListener("load", fn); }; -// Base Request URL for Python Server -export const baseUrl = `http://127.0.0.1:${port}`; - export const supportedInterfaces = guideGlobalMetadata.supported_interfaces; \ No newline at end of file diff --git a/src/renderer/src/index.ts b/src/renderer/src/index.ts index 7d25747a0..28a285273 100644 --- a/src/renderer/src/index.ts +++ b/src/renderer/src/index.ts @@ -10,7 +10,7 @@ import { } from './dependencies/globals.js' import Swal from 'sweetalert2' -import { loadServerEvents, pythonServerOpened, statusBar } from "./server.js"; +import { loadServerEvents, pythonServerOpened, statusBar } from "./server/index.js"; // Set the sidebar subtitle to the current app version const dashboard = document.querySelector('nwb-dashboard') as Dashboard diff --git a/src/renderer/src/server/globals.ts b/src/renderer/src/server/globals.ts new file mode 100644 index 000000000..2879a3151 --- /dev/null +++ b/src/renderer/src/server/globals.ts @@ -0,0 +1,79 @@ +import { isElectron, app, port } from '../electron/index.js' + +import serverSVG from "../stories/assets/server.svg?raw"; +import webAssetSVG from "../stories/assets/web_asset.svg?raw"; +import wifiSVG from "../stories/assets/wifi.svg?raw"; + +// Base Request URL for Python Server +export const baseUrl = `http://127.0.0.1:${port}`; + +const isPromise = (o) => typeof o === 'object' && typeof o.then === 'function' + +export const resolve = (object, callback) => { + if (isPromise(object)) { + return new Promise(resolvePromise => { + object.then((res) => resolvePromise((callback) ? callback(res) : res)) + }) + } else return (callback) ? callback(object) : object +} + +// ------------------------------------------------- + +import { StatusBar } from "../stories/status/StatusBar.js"; +import { unsafeSVG } from "lit/directives/unsafe-svg.js"; + +const appVersion = app?.getVersion(); + +export const statusBar = new StatusBar({ + items: [ + { label: unsafeSVG(webAssetSVG), value: isElectron ? appVersion ?? 'ERROR' : 'Web' }, + { label: unsafeSVG(wifiSVG) }, + { label: unsafeSVG(serverSVG) } + ] +}) + + +let serverCallbacks: Function[] = [] +export const onServerOpen = (callback:Function) => { + if (statusBar.items[2].status === true) return callback() + else { + return new Promise(res => { + serverCallbacks.push(() => { + res(callback()) + }) + + }) + } +} + +export const activateServer = () => { + statusBar.items[2].status = true + + serverCallbacks.forEach(cb => cb()) + serverCallbacks = [] +} + +export const serverGlobals = { + species: new Promise((res, rej) => { + onServerOpen(() => { + fetch(new URL("get-recommended-species", baseUrl)) + .then((res) => res.json()) + .then((species) => { + res(species) + serverGlobals.species = species + }) + .catch(() => rej()); + }); + }), + cpus: new Promise((res, rej) => { + onServerOpen(() => { + fetch(new URL("cpus", baseUrl)) + .then((res) => res.json()) + .then((cpus) => { + res(cpus) + serverGlobals.cpus = cpus + }) + .catch(() => rej()); + }); + }) + } \ No newline at end of file diff --git a/src/renderer/src/server.ts b/src/renderer/src/server/index.ts similarity index 57% rename from src/renderer/src/server.ts rename to src/renderer/src/server/index.ts index e04bf290e..e2dae2fcb 100644 --- a/src/renderer/src/server.ts +++ b/src/renderer/src/server/index.ts @@ -1,31 +1,13 @@ -import { isElectron, electron, app } from './electron/index.js' +import { isElectron, electron, app, port } from '../electron/index.js' const { ipcRenderer } = electron; import { notyf, -} from './dependencies/globals.js' - -import { baseUrl } from './globals.js' +} from '../dependencies/globals.js' import Swal from 'sweetalert2' - -import { StatusBar } from "./stories/status/StatusBar.js"; -import { unsafeSVG } from "lit/directives/unsafe-svg.js"; -import serverSVG from "./stories/assets/server.svg?raw"; -import webAssetSVG from "./stories/assets/web_asset.svg?raw"; -import wifiSVG from "./stories/assets/wifi.svg?raw"; - -const appVersion = app?.getVersion(); - -export const statusBar = new StatusBar({ - items: [ - { label: unsafeSVG(webAssetSVG), value: isElectron ? appVersion ?? 'ERROR' : 'Web' }, - { label: unsafeSVG(wifiSVG) }, - { label: unsafeSVG(serverSVG) } - ] -}) - +import { activateServer, baseUrl } from './globals.js'; // Check if the Flask server is live const serverIsLiveStartup = async () => { @@ -39,27 +21,6 @@ const serverIsLiveStartup = async () => { else throw new Error('Error preloading Flask imports') }) - -let serverCallbacks: Function[] = [] -export const onServerOpen = (callback:Function) => { - if (statusBar.items[2].status === true) return callback() - else { - return new Promise(res => { - serverCallbacks.push(() => { - res(callback()) - }) - - }) - } -} - -export const activateServer = () => { - statusBar.items[2].status = true - - serverCallbacks.forEach(cb => cb()) - serverCallbacks = [] -} - export async function pythonServerOpened() { // Confirm requests are actually received by the server @@ -140,40 +101,3 @@ export const loadServerEvents = () => { else activateServer() // Just mock-activate the server if we're in the browser } - - - -const isPromise = (o) => typeof o === 'object' && typeof o.then === 'function' - -export const resolve = (object, callback) => { - if (isPromise(object)) { - return new Promise(resolvePromise => { - object.then((res) => resolvePromise((callback) ? callback(res) : res)) - }) - } else return (callback) ? callback(object) : object -} - -export const serverGlobals = { - species: new Promise((res, rej) => { - onServerOpen(() => { - fetch(new URL("get-recommended-species", baseUrl)) - .then((res) => res.json()) - .then((species) => { - res(species) - serverGlobals.species = species - }) - .catch(() => rej()); - }); - }), - cpus: new Promise((res, rej) => { - onServerOpen(() => { - fetch(new URL("cpus", baseUrl)) - .then((res) => res.json()) - .then((cpus) => { - res(cpus) - serverGlobals.cpus = cpus - }) - .catch(() => rej()); - }); - }) -} \ No newline at end of file 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 48c2c5592..62be07bd7 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -3,7 +3,6 @@ import { isStorybook } from "../../../../dependencies/globals.js"; import { JSONSchemaForm } from "../../../JSONSchemaForm.js"; import { InstanceManager } from "../../../InstanceManager.js"; import { ManagedPage } from "./ManagedPage.js"; -import { baseUrl } from "../../../../globals.js"; import { onThrow } from "../../../../errors"; import { merge, sanitize } from "../../utils.js"; import preprocessSourceDataSchema from "../../../../../../../schemas/source-data.schema"; @@ -13,7 +12,8 @@ import { header } from "../../../forms/utils"; import { Button } from "../../../Button.js"; import globalIcon from "../../../assets/global.svg?raw"; -import { run } from "../options/utils.js"; + +import { baseUrl } from "../../../../server/globals.js"; const propsToIgnore = [ "verbose", 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 a365e2d5c..bb615bad3 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js @@ -3,10 +3,11 @@ import { Page } from "../../Page.js"; // For Multi-Select Form import { Button } from "../../../Button.js"; -import { baseUrl, supportedInterfaces } from "../../../../globals.js"; +import { supportedInterfaces } from "../../../../globals.js"; import { Search } from "../../../Search.js"; import { Modal } from "../../../Modal"; import { List } from "../../../List"; +import { baseUrl } from "../../../../server/globals.js"; const defaultEmptyMessage = "No interfaces selected"; diff --git a/src/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js b/src/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js index 2816a4af7..8f1bc71ae 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js +++ b/src/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js @@ -8,8 +8,8 @@ import dandiUploadSchema from "../../../../../../../schemas/dandi-upload.schema" import { dandisetInfoContent, uploadToDandi } from "../../uploads/UploadsPage.js"; import { InfoBox } from "../../../InfoBox.js"; import { until } from "lit/directives/until.js"; -import { onServerOpen } from "../../../../server"; -import { baseUrl } from "../../../../globals.js"; +import { onServerOpen } from "../../../../server/index.js"; +import { baseUrl } from "../../../../server/globals.js"; export class GuidedUploadPage extends Page { constructor(...args) { 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 68781e8ab..050da3be2 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/utils.js +++ b/src/renderer/src/stories/pages/guided-mode/options/utils.js @@ -1,7 +1,6 @@ import Swal from "sweetalert2"; -import { baseUrl } from "../../../../globals.js"; import { sanitize } from "../../utils.js"; -import { Loader } from "../../../Loader"; +import { baseUrl } from "../../../../server/globals.js"; export const openProgressSwal = (options, callback) => { return new Promise((resolve) => { diff --git a/src/renderer/src/stories/pages/preview/PreviewPage.js b/src/renderer/src/stories/pages/preview/PreviewPage.js index 45f4c5b7b..3d3ec2c80 100644 --- a/src/renderer/src/stories/pages/preview/PreviewPage.js +++ b/src/renderer/src/stories/pages/preview/PreviewPage.js @@ -3,7 +3,7 @@ import { Page } from "../Page.js"; import { onThrow } from "../../../errors"; import { JSONSchemaInput } from "../../JSONSchemaInput.js"; import { Neurosift } from "../../preview/Neurosift.js"; -import { baseUrl } from "../../../globals.js"; +import { baseUrl } from "../../../server/globals.js"; export class PreviewPage extends Page { header = { diff --git a/src/renderer/src/stories/pages/uploads/UploadsPage.js b/src/renderer/src/stories/pages/uploads/UploadsPage.js index a1b3adc5b..dca377426 100644 --- a/src/renderer/src/stories/pages/uploads/UploadsPage.js +++ b/src/renderer/src/stories/pages/uploads/UploadsPage.js @@ -26,8 +26,8 @@ import { header } from "../../forms/utils"; import { validateDANDIApiKey } from "../../../validation/dandi"; import { InfoBox } from "../../InfoBox.js"; -import { onServerOpen } from "../../../server"; -import { baseUrl } from "../../../globals.js"; +import { onServerOpen } from "../../../server/index.js"; +import { baseUrl } from "../../../server/globals.js"; export const isStaging = (id) => parseInt(id) >= 100000; diff --git a/src/renderer/src/stories/preview/Neurosift.js b/src/renderer/src/stories/preview/Neurosift.js index ee14df8bb..8def8c8dd 100644 --- a/src/renderer/src/stories/preview/Neurosift.js +++ b/src/renderer/src/stories/preview/Neurosift.js @@ -1,8 +1,8 @@ import { LitElement, css, html } from "lit"; -import { baseUrl } from "../../globals"; import { Loader } from "../Loader"; import { FullScreenToggle } from "../FullScreenToggle"; +import { baseUrl } from "../../server/globals"; export function getURLFromFilePath(file, projectName) { const regexp = new RegExp(`.+(${projectName}.+)`); diff --git a/src/renderer/src/validation/index.js b/src/renderer/src/validation/index.js index 86d13a7b8..beccffbc9 100644 --- a/src/renderer/src/validation/index.js +++ b/src/renderer/src/validation/index.js @@ -1,5 +1,5 @@ -import { baseUrl } from "../globals"; import { resolveAll } from "../promises"; +import { baseUrl } from "../server/globals"; import validationSchema from "./validation"; // NOTE: Only validation missing on NWBFile Metadata is check_subject_exists and check_processing_module_name diff --git a/tests/metadata.test.ts b/tests/metadata.test.ts index b5dab4e9e..2396154bc 100644 --- a/tests/metadata.test.ts +++ b/tests/metadata.test.ts @@ -24,6 +24,10 @@ describe('metadata is specified correctly', () => { test('session-specific metadata is merged with project and subject metadata correctly', () => { const globalState = createMockGlobalState() + + // Allow mouse (full list populated from server) + baseMetadataSchema.properties.Subject.properties.species.enum = ['Mus musculus'] + const result = mapSessions(info => createResults(info, globalState), globalState) const res = v.validate(result[0], baseMetadataSchema) // Check first session with JSON Schema expect(res.errors).toEqual([]) From 4279766ba1aba6f0fd04eed6cb53bc1f63b43bfa Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Thu, 16 Nov 2023 10:32:03 -0800 Subject: [PATCH 07/18] Fix imports --- src/renderer/src/index.ts | 4 +++- src/renderer/src/server/globals.ts | 3 ++- src/renderer/src/stories/JSONSchemaForm.js | 2 +- .../src/stories/pages/guided-mode/data/GuidedSourceData.js | 2 +- .../src/stories/pages/guided-mode/data/GuidedStructure.js | 2 +- .../src/stories/pages/guided-mode/options/GuidedUpload.js | 4 ++-- src/renderer/src/stories/pages/guided-mode/options/utils.js | 2 +- src/renderer/src/stories/pages/preview/PreviewPage.js | 2 +- src/renderer/src/stories/pages/uploads/UploadsPage.js | 3 +-- 9 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/renderer/src/index.ts b/src/renderer/src/index.ts index 28a285273..7cc801eac 100644 --- a/src/renderer/src/index.ts +++ b/src/renderer/src/index.ts @@ -10,7 +10,9 @@ import { } from './dependencies/globals.js' import Swal from 'sweetalert2' -import { loadServerEvents, pythonServerOpened, statusBar } from "./server/index.js"; +import { loadServerEvents, pythonServerOpened } from "./server/index.js"; + +import { statusBar } from "./server/globals.js"; // Set the sidebar subtitle to the current app version const dashboard = document.querySelector('nwb-dashboard') as Dashboard diff --git a/src/renderer/src/server/globals.ts b/src/renderer/src/server/globals.ts index 2879a3151..c5fc789bc 100644 --- a/src/renderer/src/server/globals.ts +++ b/src/renderer/src/server/globals.ts @@ -76,4 +76,5 @@ export const serverGlobals = { .catch(() => rej()); }); }) - } \ No newline at end of file + } + \ No newline at end of file diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index ef68b52a3..3fe751608 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -376,7 +376,7 @@ export class JSONSchemaForm extends LitElement { let message = isValid ? "" : requiredButNotSpecified.length === 1 - ? `${requiredButNotSpecified[0]} is not defined` + ? `${header(requiredButNotSpecified[0])} is not defined` : `${requiredButNotSpecified.length} required inputs are not specified properly`; if (requiredButNotSpecified.length !== nMissingRequired) console.warn("Disagreement about the correct error to throw..."); 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 62be07bd7..f8bbfcbdc 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -13,7 +13,7 @@ import { Button } from "../../../Button.js"; import globalIcon from "../../../assets/global.svg?raw"; -import { baseUrl } from "../../../../server/globals.js"; +import { baseUrl } from "../../../../server/globals"; const propsToIgnore = [ "verbose", 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 bb615bad3..92e8e30cc 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js @@ -7,7 +7,7 @@ import { supportedInterfaces } from "../../../../globals.js"; import { Search } from "../../../Search.js"; import { Modal } from "../../../Modal"; import { List } from "../../../List"; -import { baseUrl } from "../../../../server/globals.js"; +import { baseUrl } from "../../../../server/globals"; const defaultEmptyMessage = "No interfaces selected"; diff --git a/src/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js b/src/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js index 8f1bc71ae..49fd68827 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js +++ b/src/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js @@ -8,8 +8,8 @@ import dandiUploadSchema from "../../../../../../../schemas/dandi-upload.schema" import { dandisetInfoContent, uploadToDandi } from "../../uploads/UploadsPage.js"; import { InfoBox } from "../../../InfoBox.js"; import { until } from "lit/directives/until.js"; -import { onServerOpen } from "../../../../server/index.js"; -import { baseUrl } from "../../../../server/globals.js"; + +import { baseUrl, onServerOpen } from "../../../../server/globals"; export class GuidedUploadPage extends Page { constructor(...args) { 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 050da3be2..b1372c3d7 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/utils.js +++ b/src/renderer/src/stories/pages/guided-mode/options/utils.js @@ -1,6 +1,6 @@ import Swal from "sweetalert2"; import { sanitize } from "../../utils.js"; -import { baseUrl } from "../../../../server/globals.js"; +import { baseUrl } from "../../../../server/globals"; export const openProgressSwal = (options, callback) => { return new Promise((resolve) => { diff --git a/src/renderer/src/stories/pages/preview/PreviewPage.js b/src/renderer/src/stories/pages/preview/PreviewPage.js index 3d3ec2c80..be1f00070 100644 --- a/src/renderer/src/stories/pages/preview/PreviewPage.js +++ b/src/renderer/src/stories/pages/preview/PreviewPage.js @@ -3,7 +3,7 @@ import { Page } from "../Page.js"; import { onThrow } from "../../../errors"; import { JSONSchemaInput } from "../../JSONSchemaInput.js"; import { Neurosift } from "../../preview/Neurosift.js"; -import { baseUrl } from "../../../server/globals.js"; +import { baseUrl } from "../../../server/globals"; export class PreviewPage extends Page { header = { diff --git a/src/renderer/src/stories/pages/uploads/UploadsPage.js b/src/renderer/src/stories/pages/uploads/UploadsPage.js index dca377426..a63573ddf 100644 --- a/src/renderer/src/stories/pages/uploads/UploadsPage.js +++ b/src/renderer/src/stories/pages/uploads/UploadsPage.js @@ -26,8 +26,7 @@ import { header } from "../../forms/utils"; import { validateDANDIApiKey } from "../../../validation/dandi"; import { InfoBox } from "../../InfoBox.js"; -import { onServerOpen } from "../../../server/index.js"; -import { baseUrl } from "../../../server/globals.js"; +import { baseUrl, onServerOpen } from "../../../server/globals"; export const isStaging = (id) => parseInt(id) >= 100000; From e71d3dd657f5466f1114d4ee1ad0195440c9a2bc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:32:26 +0000 Subject: [PATCH 08/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/server/globals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/server/globals.ts b/src/renderer/src/server/globals.ts index c5fc789bc..749f107b1 100644 --- a/src/renderer/src/server/globals.ts +++ b/src/renderer/src/server/globals.ts @@ -77,4 +77,4 @@ export const serverGlobals = { }); }) } - \ No newline at end of file + From 0f8a230ee569161e1c1da795a43609642112d6a5 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 19:10:43 +0000 Subject: [PATCH 09/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/server/globals.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/src/server/globals.ts b/src/renderer/src/server/globals.ts index 749f107b1..7570a881e 100644 --- a/src/renderer/src/server/globals.ts +++ b/src/renderer/src/server/globals.ts @@ -77,4 +77,3 @@ export const serverGlobals = { }); }) } - From 4dfda5ad3908ea2ecf33e7ce78c85bcf9e1eb32e 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:50:59 +0000 Subject: [PATCH 10/18] [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 f6b87e47b..1b5b9c7fd 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/utils.js +++ b/src/renderer/src/stories/pages/guided-mode/options/utils.js @@ -22,12 +22,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" }; @@ -39,12 +37,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 bc6869b055d5b30a0f9562305fdfadfa90bcd61f Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 21 Nov 2023 15:08:35 -0600 Subject: [PATCH 11/18] Update value and rerender --- src/renderer/src/stories/Search.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/stories/Search.js b/src/renderer/src/stories/Search.js index b692ee333..4bdf393e8 100644 --- a/src/renderer/src/stories/Search.js +++ b/src/renderer/src/stories/Search.js @@ -174,6 +174,7 @@ export class Search extends LitElement { options: { type: Object }, showAllWhenEmpty: { type: Boolean }, listMode: { type: String, reflect: true }, + value: { type: String }, }; } From 7c372189fd63705b7e1371d891f73d9cbc3abadd Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 21 Nov 2023 15:28:23 -0600 Subject: [PATCH 12/18] Allow for setting value on input directly --- src/renderer/src/stories/JSONSchemaInput.js | 7 +++++-- src/renderer/src/stories/Search.js | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index fd96ce543..627a9854b 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -112,7 +112,8 @@ export class JSONSchemaInput extends LitElement { // onValidate = () => {} updateData(value, forceValidate = false) { - if (this.value === value && !forceValidate) { + + if (this.value !== value && !forceValidate) { const el = this.getElement(); if (el.type === "checkbox") el.checked = value; else if (el.classList.contains("list")) @@ -122,6 +123,7 @@ export class JSONSchemaInput extends LitElement { }) : []; else el.value = value; + } const { path: fullPath } = this; @@ -191,6 +193,7 @@ export class JSONSchemaInput extends LitElement { #onThrow = (...args) => (this.onThrow ? this.onThrow(...args) : this.form?.onThrow(...args)); #render() { + const { validateOnChange, info, path: fullPath } = this; const path = typeof fullPath === "string" ? fullPath.split("-") : [...fullPath]; @@ -380,7 +383,7 @@ export class JSONSchemaInput extends LitElement { }, }); - // search.classList.add("schema-input") + search.classList.add("schema-input") return search; } diff --git a/src/renderer/src/stories/Search.js b/src/renderer/src/stories/Search.js index 4bdf393e8..a174b5f4c 100644 --- a/src/renderer/src/stories/Search.js +++ b/src/renderer/src/stories/Search.js @@ -174,11 +174,12 @@ export class Search extends LitElement { options: { type: Object }, showAllWhenEmpty: { type: Boolean }, listMode: { type: String, reflect: true }, - value: { type: String }, + value: { type: String, reflect: true }, }; } updated() { + const options = this.shadowRoot.querySelectorAll(".option"); this.#options = Array.from(options).map((option) => { const keywordString = option.getAttribute("data-keywords"); @@ -256,8 +257,10 @@ export class Search extends LitElement { this.setAttribute("active", !!toShow.length); }; + render() { + this.categories = {}; // Update list From 893bda68709750777c796e23cae1aee4e76c46aa 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 21:28:40 +0000 Subject: [PATCH 13/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/JSONSchemaInput.js | 5 +---- src/renderer/src/stories/Search.js | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 627a9854b..b51f16732 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -112,7 +112,6 @@ export class JSONSchemaInput extends LitElement { // onValidate = () => {} updateData(value, forceValidate = false) { - if (this.value !== value && !forceValidate) { const el = this.getElement(); if (el.type === "checkbox") el.checked = value; @@ -123,7 +122,6 @@ export class JSONSchemaInput extends LitElement { }) : []; else el.value = value; - } const { path: fullPath } = this; @@ -193,7 +191,6 @@ export class JSONSchemaInput extends LitElement { #onThrow = (...args) => (this.onThrow ? this.onThrow(...args) : this.form?.onThrow(...args)); #render() { - const { validateOnChange, info, path: fullPath } = this; const path = typeof fullPath === "string" ? fullPath.split("-") : [...fullPath]; @@ -383,7 +380,7 @@ export class JSONSchemaInput extends LitElement { }, }); - search.classList.add("schema-input") + search.classList.add("schema-input"); return search; } diff --git a/src/renderer/src/stories/Search.js b/src/renderer/src/stories/Search.js index a174b5f4c..d22fd8ee8 100644 --- a/src/renderer/src/stories/Search.js +++ b/src/renderer/src/stories/Search.js @@ -179,7 +179,6 @@ export class Search extends LitElement { } updated() { - const options = this.shadowRoot.querySelectorAll(".option"); this.#options = Array.from(options).map((option) => { const keywordString = option.getAttribute("data-keywords"); @@ -257,10 +256,8 @@ export class Search extends LitElement { this.setAttribute("active", !!toShow.length); }; - render() { - this.categories = {}; // Update list From 5c7a24d48f771bb8d2665c2601ecfc1779e93056 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 21 Nov 2023 15:46:02 -0600 Subject: [PATCH 14/18] Properly set input --- src/renderer/src/stories/JSONSchemaInput.js | 7 ++++++- src/renderer/src/stories/Search.js | 7 +------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index b51f16732..41a6dd368 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -112,7 +112,10 @@ export class JSONSchemaInput extends LitElement { // onValidate = () => {} updateData(value, forceValidate = false) { + if (this.value !== value && !forceValidate) { + + // Update the actual input element const el = this.getElement(); if (el.type === "checkbox") el.checked = value; else if (el.classList.contains("list")) @@ -121,7 +124,8 @@ export class JSONSchemaInput extends LitElement { return { value }; }) : []; - else el.value = value; + else if (el instanceof Search) el.shadowRoot.querySelector("input").value = value; + else el.value = value } const { path: fullPath } = this; @@ -157,6 +161,7 @@ export class JSONSchemaInput extends LitElement { this.value = value; // Update the latest value this.#activateTimeoutValidation(name, path); + }; #triggerValidation = (name, path) => { diff --git a/src/renderer/src/stories/Search.js b/src/renderer/src/stories/Search.js index d22fd8ee8..e75e5dc94 100644 --- a/src/renderer/src/stories/Search.js +++ b/src/renderer/src/stories/Search.js @@ -174,7 +174,6 @@ export class Search extends LitElement { options: { type: Object }, showAllWhenEmpty: { type: Boolean }, listMode: { type: String, reflect: true }, - value: { type: String, reflect: true }, }; } @@ -187,6 +186,7 @@ export class Search extends LitElement { }); this.#initialize(); + } onSelect = (id, value) => {}; @@ -220,11 +220,6 @@ export class Search extends LitElement { #sortedCategories = []; #populate = (input) => { - // Hide all if empty - if (!input) { - this.#initialize(); - return; - } const toShow = []; From 4b7dff29efd65f92640920f80c851675372a6856 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 21:46:54 +0000 Subject: [PATCH 15/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/JSONSchemaInput.js | 5 +---- src/renderer/src/stories/Search.js | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 41a6dd368..176fddd34 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -112,9 +112,7 @@ export class JSONSchemaInput extends LitElement { // onValidate = () => {} updateData(value, forceValidate = false) { - if (this.value !== value && !forceValidate) { - // Update the actual input element const el = this.getElement(); if (el.type === "checkbox") el.checked = value; @@ -125,7 +123,7 @@ export class JSONSchemaInput extends LitElement { }) : []; else if (el instanceof Search) el.shadowRoot.querySelector("input").value = value; - else el.value = value + else el.value = value; } const { path: fullPath } = this; @@ -161,7 +159,6 @@ export class JSONSchemaInput extends LitElement { this.value = value; // Update the latest value this.#activateTimeoutValidation(name, path); - }; #triggerValidation = (name, path) => { diff --git a/src/renderer/src/stories/Search.js b/src/renderer/src/stories/Search.js index e75e5dc94..f39197608 100644 --- a/src/renderer/src/stories/Search.js +++ b/src/renderer/src/stories/Search.js @@ -186,7 +186,6 @@ export class Search extends LitElement { }); this.#initialize(); - } onSelect = (id, value) => {}; @@ -220,7 +219,6 @@ export class Search extends LitElement { #sortedCategories = []; #populate = (input) => { - const toShow = []; // Check if the input value matches the label From c279d62eb8ae2561152d047799a9bb82fe8ba9f2 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 21 Nov 2023 15:53:56 -0600 Subject: [PATCH 16/18] Ensure global properties can be removed using the preprocess script --- schemas/base-metadata.schema.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/schemas/base-metadata.schema.ts b/schemas/base-metadata.schema.ts index c76e0846a..8cd5c7912 100644 --- a/schemas/base-metadata.schema.ts +++ b/schemas/base-metadata.schema.ts @@ -34,10 +34,13 @@ function getSpeciesInfo(species: any[][] = []) { } -export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { +export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, global = false) => { + + + const copy = structuredClone(schema) // Add unit to weight - const subjectProps = schema.properties.Subject.properties + const subjectProps = copy.properties.Subject.properties subjectProps.weight.unit = 'kg' // subjectProps.order = ['weight', 'age', 'age__reference', 'date_of_birth', 'genotype', 'strain'] @@ -67,11 +70,21 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema) => { }) // Ensure experimenter schema has custom structure - schema.properties.NWBFile.properties.experimenter = baseMetadataSchema.properties.NWBFile.properties.experimenter + copy.properties.NWBFile.properties.experimenter = baseMetadataSchema.properties.NWBFile.properties.experimenter // 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 - return schema + copy.properties.NWBFile.properties.keywords.description = 'Terms to describe your dataset (e.g. Neural circuits, V1, etc.)' // Add description to keywords + + + + // Remove non-global properties + if (global) { + Object.entries(copy.properties).forEach(([globalProp, schema]) => { + instanceSpecificFields[globalProp]?.forEach((prop) => delete schema.properties[prop]); + }); + } + + return copy } @@ -93,13 +106,7 @@ export const instanceSpecificFields = { }; -const globalSchema = structuredClone(preprocessMetadataSchema()); -Object.entries(globalSchema.properties).forEach(([globalProp, schema]) => { - instanceSpecificFields[globalProp]?.forEach((prop) => delete schema.properties[prop]); -}); +export const globalSchema = preprocessMetadataSchema(undefined, true); -export { - globalSchema -} export default preprocessMetadataSchema() From ec587eb3e6f184514798a20d02ef6d8c47837a65 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 21 Nov 2023 17:02:37 -0600 Subject: [PATCH 17/18] Use globals when necessary --- .../src/stories/pages/guided-mode/data/GuidedMetadata.js | 2 +- .../src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js | 2 +- .../src/stories/pages/guided-mode/setup/GuidedSubjects.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 f06be4cc5..14de7a9bb 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -85,7 +85,7 @@ export class GuidedMetadataPage extends ManagedPage { const modal = (this.#globalModal = createGlobalFormModal.call(this, { header: "Global Metadata", propsToRemove: [...propsToIgnore], - schema: preprocessMetadataSchema(), // Provide HARDCODED global schema for metadata properties (not automatically abstracting across sessions)... + schema: preprocessMetadataSchema(undefined, true), // Provide HARDCODED global schema for metadata properties (not automatically abstracting across sessions)... hasInstances: true, mergeFunction: function (globalResolved, globals) { merge(globalResolved, globals); diff --git a/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js b/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js index a206644b1..f0a01b18f 100644 --- a/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js +++ b/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js @@ -88,7 +88,7 @@ export class GuidedNewDatasetPage extends Page { const pages = schemaToPages.call( this, - preprocessMetadataSchema(), + preprocessMetadataSchema(undefined, true), ["project"], { validateEmptyValues: false }, (info) => { 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 b7b2f6622..c9c18f5d4 100644 --- a/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js +++ b/src/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js @@ -84,7 +84,7 @@ export class GuidedSubjectsPage extends Page { const modal = (this.#globalModal = createGlobalFormModal.call(this, { header: "Global Subject Metadata", key: "Subject", - schema: preprocessMetadataSchema().properties.Subject, + schema: preprocessMetadataSchema(undefined, true).properties.Subject, validateOnChange: (key, parent, path) => { return validateOnChange(key, parent, ["Subject", ...path]); }, From 728f9754ccf5f9120b00c5db4e13d8296e35f387 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 21 Nov 2023 21:13:47 -0600 Subject: [PATCH 18/18] Update storyStates.ts --- src/renderer/src/stories/pages/guided-mode/storyStates.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/renderer/src/stories/pages/guided-mode/storyStates.ts b/src/renderer/src/stories/pages/guided-mode/storyStates.ts index c9e529244..211ee2c5c 100644 --- a/src/renderer/src/stories/pages/guided-mode/storyStates.ts +++ b/src/renderer/src/stories/pages/guided-mode/storyStates.ts @@ -2,8 +2,7 @@ import nwbBaseSchema from "../../../../../../schemas/base-metadata.schema"; // import exephysExampleSchema from "../../../../../../schemas/json/ecephys_metadata_schema_example.json"; import { dashboard } from "../../../pages.js"; -import { activateServer } from "../../../server"; - +import { activateServer } from "../../../server/globals"; activateServer();