From c2cc0b5048396229173239b79a30e45899155a9c Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Wed, 22 May 2024 10:42:41 -0700 Subject: [PATCH 1/6] Separate stories from the src --- .storybook/main.ts | 2 +- generateInterfaceSchema.py | 6 +- .../stories/assets => assets/icons}/dandi.svg | 0 .../assets => assets/icons}/delete.svg | 0 .../assets => assets/icons}/exploration.svg | 0 .../assets => assets/icons}/folder_open.svg | 0 .../assets => assets/icons}/fullscreen.svg | 0 .../icons}/fullscreen_exit.svg | 0 .../assets => assets/icons}/global.svg | 0 .../assets => assets/icons}/inspect.svg | 0 .../stories/assets => assets/icons}/key.svg | 0 .../icons}/neurosift-logo.svg | 0 .../assets => assets/icons}/preview.svg | 0 .../assets => assets/icons}/python.svg | 0 .../assets => assets/icons}/restart.svg | 0 .../stories/assets => assets/icons}/save.svg | 0 .../assets => assets/icons}/search.svg | 0 .../assets => assets/icons}/server.svg | 0 .../assets => assets/icons}/settings.svg | 0 .../assets => assets/icons}/web_asset.svg | 0 .../stories/assets => assets/icons}/wifi.svg | 0 src/electron/renderer/src/pages.js | 8 +- src/electron/renderer/src/server/globals.ts | 6 +- .../src/stories/FileSystemSelector.js | 2 +- .../renderer/src/stories/FullScreenToggle.ts | 4 +- src/electron/renderer/src/stories/Search.js | 2 +- .../pages/guided-mode/data/GuidedMetadata.js | 2 +- .../guided-mode/data/GuidedPathExpansion.js | 2 +- .../guided-mode/data/GuidedSourceData.js | 2 +- .../options/GuidedInspectorPage.js | 2 +- .../guided-mode/options/GuidedStubPreview.js | 2 +- .../pages/guided-mode/options/GuidedUpload.js | 2 +- .../guided-mode/results/GuidedResults.js | 2 +- .../pages/guided-mode/setup/GuidedSubjects.js | 2 +- .../stories/pages/settings/SettingsPage.js | 8 +- .../src/stories/pages/uploads/UploadsPage.js | 2 +- .../src/stories => stories}/Introduction.mdx | 0 .../pages => stories}/Pages.stories.js | 2 +- .../assets/code-brackets.svg | 0 .../src/stories => stories}/assets/colors.svg | 0 .../stories => stories}/assets/comments.svg | 0 .../stories => stories}/assets/direction.svg | 0 .../src/stories => stories}/assets/flow.svg | 0 .../src/stories => stories}/assets/plugin.svg | 0 .../src/stories => stories}/assets/repo.svg | 0 .../stories => stories}/assets/stackalt.svg | 0 .../components}/Accordion.stories.js | 2 +- .../components}/Button.stories.js | 2 +- .../components}/FileSystemSelector.stories.js | 2 +- .../components}/InspectorList.stories.js | 4 +- .../components}/InstanceManager.stories.js | 4 +- .../components}/JSONSchemaForm.stories.js | 2 +- .../components}/List.stories.js | 2 +- .../components}/Locate.stories.js | 2 +- .../components}/Multiselect.stories.js | 2 +- .../components}/OptionalSection.stories.js | 2 +- .../components}/ProgressBar.stories.js | 2 +- .../components}/Search.stories.js | 2 +- .../components}/StatusBar.stories.js | 8 +- .../components}/Table.stories.js | 8 +- .../inputs/inspector_output.json | 0 .../AlphaOmegaRecordingInterface.json | 0 .../interface_schemas}/AudioInterface.json | 0 .../AxonaRecordingInterface.json | 0 .../BiocamRecordingInterface.json | 0 .../BlackrockRecordingInterface.json | 0 .../BlackrockSortingInterface.json | 0 .../BrukerTiffMultiPlaneConverter.json | 0 .../BrukerTiffMultiPlaneImagingInterface.json | 0 .../BrukerTiffSinglePlaneConverter.json | 0 ...BrukerTiffSinglePlaneImagingInterface.json | 0 .../CaimanSegmentationInterface.json | 0 .../CellExplorerRecordingInterface.json | 0 .../CellExplorerSortingInterface.json | 0 .../CnmfeSegmentationInterface.json | 0 .../DeepLabCutInterface.json | 0 .../EDFRecordingInterface.json | 0 .../ExtractSegmentationInterface.json | 0 .../FicTracDataInterface.json | 0 .../IntanRecordingInterface.json | 0 .../KiloSortSortingInterface.json | 0 .../MCSRawRecordingInterface.json | 0 .../MEArecRecordingInterface.json | 0 .../MicroManagerTiffImagingInterface.json | 0 .../MiniscopeBehaviorInterface.json | 0 .../MiniscopeConverter.json | 0 .../MiniscopeImagingInterface.json | 0 .../NeuralynxRecordingInterface.json | 0 .../NeuroScopeLFPInterface.json | 0 .../NeuroScopeRecordingInterface.json | 0 .../NeuroScopeSortingInterface.json | 0 .../OpenEphysRecordingInterface.json | 0 .../PhySortingInterface.json | 0 .../PlexonRecordingInterface.json | 0 .../PlexonSortingInterface.json | 0 .../interface_schemas}/SLEAPInterface.json | 0 .../SbxImagingInterface.json | 0 .../ScanImageImagingInterface.json | 0 .../Spike2RecordingInterface.json | 0 .../SpikeGLXConverterPipe.json | 0 .../SpikeGLXNIDQInterface.json | 0 .../SpikeGLXRecordingInterface.json | 0 .../Suite2pSegmentationInterface.json | 0 .../TdtRecordingInterface.json | 0 .../TiffImagingInterface.json | 0 .../interface_schemas}/VideoInterface.json | 0 .../pages}/GuidedHome.stories.js | 0 .../pages}/Metadata.stories.js | 0 .../pages}/NewDataset.stories.js | 0 .../pages}/Preview.stories.js | 0 .../pages}/Review.stories.js | 0 .../pages}/SourceData.stories.js | 90 +++++++++---------- .../pages}/Structure.stories.js | 0 .../pages}/Subjects.stories.js | 0 .../pages}/Upload.stories.js | 0 .../pages}/storyStates.ts | 6 +- tsconfig.json | 2 +- 117 files changed, 100 insertions(+), 100 deletions(-) rename src/electron/renderer/{src/stories/assets => assets/icons}/dandi.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/delete.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/exploration.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/folder_open.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/fullscreen.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/fullscreen_exit.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/global.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/inspect.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/key.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/neurosift-logo.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/preview.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/python.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/restart.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/save.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/search.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/server.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/settings.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/web_asset.svg (100%) rename src/electron/renderer/{src/stories/assets => assets/icons}/wifi.svg (100%) rename {src/electron/renderer/src/stories => stories}/Introduction.mdx (100%) rename {src/electron/renderer/src/stories/pages => stories}/Pages.stories.js (91%) rename {src/electron/renderer/src/stories => stories}/assets/code-brackets.svg (100%) rename {src/electron/renderer/src/stories => stories}/assets/colors.svg (100%) rename {src/electron/renderer/src/stories => stories}/assets/comments.svg (100%) rename {src/electron/renderer/src/stories => stories}/assets/direction.svg (100%) rename {src/electron/renderer/src/stories => stories}/assets/flow.svg (100%) rename {src/electron/renderer/src/stories => stories}/assets/plugin.svg (100%) rename {src/electron/renderer/src/stories => stories}/assets/repo.svg (100%) rename {src/electron/renderer/src/stories => stories}/assets/stackalt.svg (100%) rename {src/electron/renderer/src/stories => stories/components}/Accordion.stories.js (90%) rename {src/electron/renderer/src/stories => stories/components}/Button.stories.js (92%) rename {src/electron/renderer/src/stories => stories/components}/FileSystemSelector.stories.js (87%) rename {src/electron/renderer/src/stories/preview/inspector => stories/components}/InspectorList.stories.js (55%) rename {src/electron/renderer/src/stories => stories/components}/InstanceManager.stories.js (92%) rename {src/electron/renderer/src/stories => stories/components}/JSONSchemaForm.stories.js (96%) rename {src/electron/renderer/src/stories => stories/components}/List.stories.js (91%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/components}/Locate.stories.js (90%) rename {src/electron/renderer/src/stories/multiselect => stories/components}/Multiselect.stories.js (87%) rename {src/electron/renderer/src/stories => stories/components}/OptionalSection.stories.js (77%) rename {src/electron/renderer/src/stories => stories/components}/ProgressBar.stories.js (87%) rename {src/electron/renderer/src/stories => stories/components}/Search.stories.js (94%) rename {src/electron/renderer/src/stories/status => stories/components}/StatusBar.stories.js (63%) rename {src/electron/renderer/src/stories => stories/components}/Table.stories.js (82%) rename src/electron/renderer/src/stories/preview/inspector/test.json => stories/inputs/inspector_output.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/AlphaOmegaRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/AudioInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/AxonaRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/BiocamRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/BlackrockRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/BlackrockSortingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/BrukerTiffMultiPlaneConverter.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/BrukerTiffMultiPlaneImagingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/BrukerTiffSinglePlaneConverter.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/BrukerTiffSinglePlaneImagingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/CaimanSegmentationInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/CellExplorerRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/CellExplorerSortingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/CnmfeSegmentationInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/DeepLabCutInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/EDFRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/ExtractSegmentationInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/FicTracDataInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/IntanRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/KiloSortSortingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/MCSRawRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/MEArecRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/MicroManagerTiffImagingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/MiniscopeBehaviorInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/MiniscopeConverter.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/MiniscopeImagingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/NeuralynxRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/NeuroScopeLFPInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/NeuroScopeRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/NeuroScopeSortingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/OpenEphysRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/PhySortingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/PlexonRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/PlexonSortingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/SLEAPInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/SbxImagingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/ScanImageImagingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/Spike2RecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/SpikeGLXConverterPipe.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/SpikeGLXNIDQInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/SpikeGLXRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/Suite2pSegmentationInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/TdtRecordingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/TiffImagingInterface.json (100%) rename {src/schemas/json/generated => stories/inputs/interface_schemas}/VideoInterface.json (100%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/GuidedHome.stories.js (100%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/Metadata.stories.js (100%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/NewDataset.stories.js (100%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/Preview.stories.js (100%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/Review.stories.js (100%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/SourceData.stories.js (83%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/Structure.stories.js (100%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/Subjects.stories.js (100%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/Upload.stories.js (100%) rename {src/electron/renderer/src/stories/pages/guided-mode => stories/pages}/storyStates.ts (87%) diff --git a/.storybook/main.ts b/.storybook/main.ts index 80af1da82..4325b84cf 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -3,7 +3,7 @@ import type { StorybookConfig } from '@storybook/web-components-vite'; import ViteYaml from "@modyfi/vite-plugin-yaml"; const config: StorybookConfig = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + stories: ["../stories/**/*.mdx", "../stories/**/*.stories.@(js|jsx|ts|tsx)"], addons: ["@storybook/addon-links", "@storybook/addon-essentials"], framework: { name: "@storybook/web-components-vite", diff --git a/generateInterfaceSchema.py b/generateInterfaceSchema.py index 279c450b7..fe886ac86 100644 --- a/generateInterfaceSchema.py +++ b/generateInterfaceSchema.py @@ -4,7 +4,7 @@ from neuroconv import NWBConverter, converters, datainterfaces filepath = Path("src") / "supported_interfaces.json" -generatedJSONSchemaPath = Path("schemas", "json", "generated") +generatedJSONSchemaPath = Path("stories") / "inputs" / "interface_schemas" generatedJSONSchemaPath.mkdir(exist_ok=True, parents=True) f = filepath.open() @@ -29,9 +29,9 @@ class CustomNWBConverter(NWBConverter): outfile.write(json.dumps(schema, indent=4)) -sourceDataStoryPath = Path("src/electron/renderer/src/stories/pages/guided-mode/SourceData.stories.js") +sourceDataStoryPath = Path("stories/pages/SourceData.stories.js") -importCode = "\n".join(map(lambda arr: f"import {arr[0]}Schema from '../../../../../../{arr[1]}'", paths.items())) +importCode = "\n".join(map(lambda arr: f"import {arr[0]}Schema from '../inputs/interface_schemas/{arr[1]}'", paths.items())) storyCode = "\n".join( map( lambda arr: f"""export const {arr[0]} = PageTemplate.bind({{}}); diff --git a/src/electron/renderer/src/stories/assets/dandi.svg b/src/electron/renderer/assets/icons/dandi.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/dandi.svg rename to src/electron/renderer/assets/icons/dandi.svg diff --git a/src/electron/renderer/src/stories/assets/delete.svg b/src/electron/renderer/assets/icons/delete.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/delete.svg rename to src/electron/renderer/assets/icons/delete.svg diff --git a/src/electron/renderer/src/stories/assets/exploration.svg b/src/electron/renderer/assets/icons/exploration.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/exploration.svg rename to src/electron/renderer/assets/icons/exploration.svg diff --git a/src/electron/renderer/src/stories/assets/folder_open.svg b/src/electron/renderer/assets/icons/folder_open.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/folder_open.svg rename to src/electron/renderer/assets/icons/folder_open.svg diff --git a/src/electron/renderer/src/stories/assets/fullscreen.svg b/src/electron/renderer/assets/icons/fullscreen.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/fullscreen.svg rename to src/electron/renderer/assets/icons/fullscreen.svg diff --git a/src/electron/renderer/src/stories/assets/fullscreen_exit.svg b/src/electron/renderer/assets/icons/fullscreen_exit.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/fullscreen_exit.svg rename to src/electron/renderer/assets/icons/fullscreen_exit.svg diff --git a/src/electron/renderer/src/stories/assets/global.svg b/src/electron/renderer/assets/icons/global.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/global.svg rename to src/electron/renderer/assets/icons/global.svg diff --git a/src/electron/renderer/src/stories/assets/inspect.svg b/src/electron/renderer/assets/icons/inspect.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/inspect.svg rename to src/electron/renderer/assets/icons/inspect.svg diff --git a/src/electron/renderer/src/stories/assets/key.svg b/src/electron/renderer/assets/icons/key.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/key.svg rename to src/electron/renderer/assets/icons/key.svg diff --git a/src/electron/renderer/src/stories/assets/neurosift-logo.svg b/src/electron/renderer/assets/icons/neurosift-logo.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/neurosift-logo.svg rename to src/electron/renderer/assets/icons/neurosift-logo.svg diff --git a/src/electron/renderer/src/stories/assets/preview.svg b/src/electron/renderer/assets/icons/preview.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/preview.svg rename to src/electron/renderer/assets/icons/preview.svg diff --git a/src/electron/renderer/src/stories/assets/python.svg b/src/electron/renderer/assets/icons/python.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/python.svg rename to src/electron/renderer/assets/icons/python.svg diff --git a/src/electron/renderer/src/stories/assets/restart.svg b/src/electron/renderer/assets/icons/restart.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/restart.svg rename to src/electron/renderer/assets/icons/restart.svg diff --git a/src/electron/renderer/src/stories/assets/save.svg b/src/electron/renderer/assets/icons/save.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/save.svg rename to src/electron/renderer/assets/icons/save.svg diff --git a/src/electron/renderer/src/stories/assets/search.svg b/src/electron/renderer/assets/icons/search.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/search.svg rename to src/electron/renderer/assets/icons/search.svg diff --git a/src/electron/renderer/src/stories/assets/server.svg b/src/electron/renderer/assets/icons/server.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/server.svg rename to src/electron/renderer/assets/icons/server.svg diff --git a/src/electron/renderer/src/stories/assets/settings.svg b/src/electron/renderer/assets/icons/settings.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/settings.svg rename to src/electron/renderer/assets/icons/settings.svg diff --git a/src/electron/renderer/src/stories/assets/web_asset.svg b/src/electron/renderer/assets/icons/web_asset.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/web_asset.svg rename to src/electron/renderer/assets/icons/web_asset.svg diff --git a/src/electron/renderer/src/stories/assets/wifi.svg b/src/electron/renderer/assets/icons/wifi.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/wifi.svg rename to src/electron/renderer/assets/icons/wifi.svg diff --git a/src/electron/renderer/src/pages.js b/src/electron/renderer/src/pages.js index 4cadf8964..5b5e4117a 100644 --- a/src/electron/renderer/src/pages.js +++ b/src/electron/renderer/src/pages.js @@ -16,11 +16,11 @@ import { GuidedInspectorPage } from "./stories/pages/guided-mode/options/GuidedI import logo from "../assets/img/logo-guide-draft-transparent-tight.png"; import { GuidedPathExpansionPage } from "./stories/pages/guided-mode/data/GuidedPathExpansion"; -import uploadIcon from "./stories/assets/dandi.svg?raw"; -import inspectIcon from "./stories/assets/inspect.svg?raw"; -import neurosiftIcon from "./stories/assets/neurosift-logo.svg?raw"; +import uploadIcon from "../assets/icons/dandi.svg?raw"; +import inspectIcon from "../assets/icons/inspect.svg?raw"; +import neurosiftIcon from "../assets/icons/neurosift-logo.svg?raw"; -import settingsIcon from "./stories/assets/settings.svg?raw"; +import settingsIcon from "../assets/icons/settings.svg?raw"; import { UploadsPage } from "./stories/pages/uploads/UploadsPage"; import { SettingsPage } from "./stories/pages/settings/SettingsPage"; diff --git a/src/electron/renderer/src/server/globals.ts b/src/electron/renderer/src/server/globals.ts index 698243dc9..132d792db 100644 --- a/src/electron/renderer/src/server/globals.ts +++ b/src/electron/renderer/src/server/globals.ts @@ -1,8 +1,8 @@ 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"; +import serverSVG from "../../assets/icons/server.svg?raw"; +import webAssetSVG from "../../assets/icons/web_asset.svg?raw"; +import wifiSVG from "../../assets/icons/wifi.svg?raw"; // Base Request URL for Python Server export const baseUrl = `http://127.0.0.1:${port}`; diff --git a/src/electron/renderer/src/stories/FileSystemSelector.js b/src/electron/renderer/src/stories/FileSystemSelector.js index 8fa45febf..1a64ce91f 100644 --- a/src/electron/renderer/src/stories/FileSystemSelector.js +++ b/src/electron/renderer/src/stories/FileSystemSelector.js @@ -4,7 +4,7 @@ import { fs, remote } from "../electron/index"; import { List } from "./List"; const { dialog } = remote; -import restartSVG from "./assets/restart.svg?raw"; +import restartSVG from "../../assets/icons/restart.svg?raw"; import { unsafeSVG } from "lit/directives/unsafe-svg.js"; function getObjectTypeReferenceString(type, multiple, { nested, native } = {}) { diff --git a/src/electron/renderer/src/stories/FullScreenToggle.ts b/src/electron/renderer/src/stories/FullScreenToggle.ts index ff1e1557f..fb01e32d9 100644 --- a/src/electron/renderer/src/stories/FullScreenToggle.ts +++ b/src/electron/renderer/src/stories/FullScreenToggle.ts @@ -1,7 +1,7 @@ import { LitElement, css, html } from "lit"; -import fullScreenIcon from './assets/fullscreen.svg?raw' -import fullScreenExitIcon from './assets/fullscreen_exit.svg?raw' +import fullScreenIcon from '../../assets/icons/fullscreen.svg?raw' +import fullScreenExitIcon from '../../assets/icons/fullscreen_exit.svg?raw' import { unsafeHTML } from "lit/directives/unsafe-html.js"; diff --git a/src/electron/renderer/src/stories/Search.js b/src/electron/renderer/src/stories/Search.js index ddf87e403..023a775f8 100644 --- a/src/electron/renderer/src/stories/Search.js +++ b/src/electron/renderer/src/stories/Search.js @@ -1,7 +1,7 @@ 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/icons/search.svg?raw"; import tippy from "tippy.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; diff --git a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js index ee1e60e15..18e463156 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -23,7 +23,7 @@ import { header, tempPropertyKey } from "../../../forms/utils"; import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; import { Button } from "../../../Button.js"; -import globalIcon from "../../../assets/global.svg?raw"; +import globalIcon from "../../../../../assets/icons/global.svg?raw"; const parentTableRenderConfig = { Electrodes: (metadata) => { diff --git a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js index 922331770..0a2d26aec 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js @@ -14,7 +14,7 @@ import { Button } from "../../../Button.js"; import { Modal } from "../../../Modal"; import { header } from "../../../forms/utils"; -import autocompleteIcon from "../../../assets/inspect.svg?raw"; +import autocompleteIcon from "../../../../../assets/icons/inspect.svg?raw"; const propOrder = ["path", "subject_id", "session_id"]; diff --git a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js index 9fca8680c..cb6487423 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -11,7 +11,7 @@ import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; import { header } from "../../../forms/utils"; import { Button } from "../../../Button.js"; -import globalIcon from "../../../assets/global.svg?raw"; +import globalIcon from "../../../../../assets/icons/global.svg?raw"; import { baseUrl } from "../../../../server/globals"; diff --git a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js index 73e9b12cf..b9524020a 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js @@ -2,7 +2,7 @@ import { html } from "lit"; import { Page } from "../../Page.js"; import { unsafeSVG } from "lit/directives/unsafe-svg.js"; -import folderOpenSVG from "../../../assets/folder_open.svg?raw"; +import folderOpenSVG from "../../../../../assets/icons/folder_open.svg?raw"; import { electron } from "../../../../electron/index.js"; import { getSharedPath, removeFilePaths, truncateFilePaths } from "../../../preview/NWBFilePreview.js"; diff --git a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedStubPreview.js b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedStubPreview.js index 39635033c..9ce737ab1 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedStubPreview.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedStubPreview.js @@ -2,7 +2,7 @@ import { html } from "lit"; import { Page } from "../../Page.js"; import { unsafeSVG } from "lit/directives/unsafe-svg.js"; -import folderOpenSVG from "../../../assets/folder_open.svg?raw"; +import folderOpenSVG from "../../../../../assets/icons/folder_open.svg?raw"; import { electron } from "../../../../electron/index.js"; import { NWBFilePreview, getSharedPath } from "../../../preview/NWBFilePreview.js"; diff --git a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js index ad3d54216..92910b69f 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js @@ -10,7 +10,7 @@ import { until } from "lit/directives/until.js"; import { Button } from "../../../Button.js"; -import keyIcon from "../../../assets/key.svg?raw"; +import keyIcon from "../../../../../assets/icons/key.svg?raw"; import { validate } from "../../uploads/utils"; import { global } from "../../../../progress/index.js"; diff --git a/src/electron/renderer/src/stories/pages/guided-mode/results/GuidedResults.js b/src/electron/renderer/src/stories/pages/guided-mode/results/GuidedResults.js index 0d09797f9..a041ac937 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/results/GuidedResults.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/results/GuidedResults.js @@ -1,6 +1,6 @@ import { html } from "lit"; import { unsafeSVG } from "lit/directives/unsafe-svg.js"; -import folderOpenSVG from "../../../assets/folder_open.svg?raw"; +import folderOpenSVG from "../../../../../assets/icons/folder_open.svg?raw"; import { Page } from "../../Page.js"; import { getStubArray } from "../options/GuidedStubPreview.js"; diff --git a/src/electron/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js b/src/electron/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js index 2bce759ff..aa0aaf32f 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js @@ -10,7 +10,7 @@ import { Button } from "../../../Button.js"; import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; import { header } from "../../../forms/utils"; -import globalIcon from "../../../assets/global.svg?raw"; +import globalIcon from "../../../../../assets/icons/global.svg?raw"; export class GuidedSubjectsPage extends Page { constructor(...args) { diff --git a/src/electron/renderer/src/stories/pages/settings/SettingsPage.js b/src/electron/renderer/src/stories/pages/settings/SettingsPage.js index 136a197a3..9f72a1676 100644 --- a/src/electron/renderer/src/stories/pages/settings/SettingsPage.js +++ b/src/electron/renderer/src/stories/pages/settings/SettingsPage.js @@ -15,10 +15,10 @@ import { merge, setUndefinedIfNotDeclared } from "../utils"; import { homeDirectory, notyf, testDataFolderPath } from "../../../dependencies/globals"; import { SERVER_FILE_PATH, electron, path, port, fs } from "../../../electron/index.js"; -import saveSVG from "../../assets/save.svg?raw"; -import folderSVG from "../../assets/folder_open.svg?raw"; -import deleteSVG from "../../assets/delete.svg?raw"; -import generateSVG from "../../assets/restart.svg?raw"; +import saveSVG from "../../../../assets/icons/save.svg?raw"; +import folderSVG from "../../../../assets/icons/folder_open.svg?raw"; +import deleteSVG from "../../../../assets/icons/delete.svg?raw"; +import generateSVG from "../../../../assets/icons/restart.svg?raw"; import { header } from "../../forms/utils"; diff --git a/src/electron/renderer/src/stories/pages/uploads/UploadsPage.js b/src/electron/renderer/src/stories/pages/uploads/UploadsPage.js index 07aea9858..c0ab9e601 100644 --- a/src/electron/renderer/src/stories/pages/uploads/UploadsPage.js +++ b/src/electron/renderer/src/stories/pages/uploads/UploadsPage.js @@ -32,7 +32,7 @@ import { validateDANDIApiKey } from "../../../validation/dandi"; import * as dandi from "dandi"; -import keyIcon from "../../assets/key.svg?raw"; +import keyIcon from "../../../../assets/icons/key.svg?raw"; import { AWARD_VALIDATION_FAIL_MESSAGE, awardNumberValidator, isStaging, validate } from "./utils"; import { createFormModal } from "../../forms/GlobalFormModal"; diff --git a/src/electron/renderer/src/stories/Introduction.mdx b/stories/Introduction.mdx similarity index 100% rename from src/electron/renderer/src/stories/Introduction.mdx rename to stories/Introduction.mdx diff --git a/src/electron/renderer/src/stories/pages/Pages.stories.js b/stories/Pages.stories.js similarity index 91% rename from src/electron/renderer/src/stories/pages/Pages.stories.js rename to stories/Pages.stories.js index 24ddca68f..f72751a46 100644 --- a/src/electron/renderer/src/stories/pages/Pages.stories.js +++ b/stories/Pages.stories.js @@ -1,4 +1,4 @@ -import { dashboard } from "../../pages.js"; +import { dashboard } from "../src/electron/renderer/src/pages.js"; // const options = Object.keys(dashboard.pagesById) diff --git a/src/electron/renderer/src/stories/assets/code-brackets.svg b/stories/assets/code-brackets.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/code-brackets.svg rename to stories/assets/code-brackets.svg diff --git a/src/electron/renderer/src/stories/assets/colors.svg b/stories/assets/colors.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/colors.svg rename to stories/assets/colors.svg diff --git a/src/electron/renderer/src/stories/assets/comments.svg b/stories/assets/comments.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/comments.svg rename to stories/assets/comments.svg diff --git a/src/electron/renderer/src/stories/assets/direction.svg b/stories/assets/direction.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/direction.svg rename to stories/assets/direction.svg diff --git a/src/electron/renderer/src/stories/assets/flow.svg b/stories/assets/flow.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/flow.svg rename to stories/assets/flow.svg diff --git a/src/electron/renderer/src/stories/assets/plugin.svg b/stories/assets/plugin.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/plugin.svg rename to stories/assets/plugin.svg diff --git a/src/electron/renderer/src/stories/assets/repo.svg b/stories/assets/repo.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/repo.svg rename to stories/assets/repo.svg diff --git a/src/electron/renderer/src/stories/assets/stackalt.svg b/stories/assets/stackalt.svg similarity index 100% rename from src/electron/renderer/src/stories/assets/stackalt.svg rename to stories/assets/stackalt.svg diff --git a/src/electron/renderer/src/stories/Accordion.stories.js b/stories/components/Accordion.stories.js similarity index 90% rename from src/electron/renderer/src/stories/Accordion.stories.js rename to stories/components/Accordion.stories.js index c9dd7b05f..781198531 100644 --- a/src/electron/renderer/src/stories/Accordion.stories.js +++ b/stories/components/Accordion.stories.js @@ -1,4 +1,4 @@ -import { Accordion } from "./Accordion"; +import { Accordion } from "../../src/electron/renderer/src/stories/Accordion"; export default { title: "Components/Accordion", diff --git a/src/electron/renderer/src/stories/Button.stories.js b/stories/components/Button.stories.js similarity index 92% rename from src/electron/renderer/src/stories/Button.stories.js rename to stories/components/Button.stories.js index 08dd0185a..eece1e312 100644 --- a/src/electron/renderer/src/stories/Button.stories.js +++ b/stories/components/Button.stories.js @@ -1,4 +1,4 @@ -import { Button } from "./Button"; +import { Button } from "../../src/electron/renderer/src/stories/Button"; // More on how to set up stories at: https://storybook.js.org/docs/7.0/web-components/writing-stories/introduction export default { diff --git a/src/electron/renderer/src/stories/FileSystemSelector.stories.js b/stories/components/FileSystemSelector.stories.js similarity index 87% rename from src/electron/renderer/src/stories/FileSystemSelector.stories.js rename to stories/components/FileSystemSelector.stories.js index 75ba900eb..282598976 100644 --- a/src/electron/renderer/src/stories/FileSystemSelector.stories.js +++ b/stories/components/FileSystemSelector.stories.js @@ -1,4 +1,4 @@ -import { FilesystemSelector } from "./FileSystemSelector"; +import { FilesystemSelector } from "../../src/electron/renderer/src/stories/FileSystemSelector"; export default { title: "Components/Filesystem Selector", diff --git a/src/electron/renderer/src/stories/preview/inspector/InspectorList.stories.js b/stories/components/InspectorList.stories.js similarity index 55% rename from src/electron/renderer/src/stories/preview/inspector/InspectorList.stories.js rename to stories/components/InspectorList.stories.js index bcb331913..16a2b91da 100644 --- a/src/electron/renderer/src/stories/preview/inspector/InspectorList.stories.js +++ b/stories/components/InspectorList.stories.js @@ -1,5 +1,5 @@ -import { InspectorList } from "./InspectorList"; -import testInspectorList from "./test.json"; +import { InspectorList } from "../../src/electron/renderer/src/stories/preview/inspector/InspectorList"; +import testInspectorList from "../data/inspector_output.json"; export default { title: "Components/Inspector List", diff --git a/src/electron/renderer/src/stories/InstanceManager.stories.js b/stories/components/InstanceManager.stories.js similarity index 92% rename from src/electron/renderer/src/stories/InstanceManager.stories.js rename to stories/components/InstanceManager.stories.js index 4442ec890..098fd55be 100644 --- a/src/electron/renderer/src/stories/InstanceManager.stories.js +++ b/stories/components/InstanceManager.stories.js @@ -1,5 +1,5 @@ -import { InstanceManager } from "./InstanceManager"; -import { Modal } from "./Modal"; +import { InstanceManager } from "../../src/electron/renderer/src/stories/InstanceManager"; +import { Modal } from "../../src/electron/renderer/src/stories/Modal"; // More on how to set up stories at: https://storybook.js.org/docs/7.0/web-components/writing-stories/introduction export default { diff --git a/src/electron/renderer/src/stories/JSONSchemaForm.stories.js b/stories/components/JSONSchemaForm.stories.js similarity index 96% rename from src/electron/renderer/src/stories/JSONSchemaForm.stories.js rename to stories/components/JSONSchemaForm.stories.js index bbaa8b48b..3c038f0fa 100644 --- a/src/electron/renderer/src/stories/JSONSchemaForm.stories.js +++ b/stories/components/JSONSchemaForm.stories.js @@ -1,4 +1,4 @@ -import { JSONSchemaForm } from "./JSONSchemaForm"; +import { JSONSchemaForm } from "../../src/electron/renderer/src/stories/JSONSchemaForm"; export default { title: "Components/JSON Schema Form", diff --git a/src/electron/renderer/src/stories/List.stories.js b/stories/components/List.stories.js similarity index 91% rename from src/electron/renderer/src/stories/List.stories.js rename to stories/components/List.stories.js index 9ebb39a89..a40a097d6 100644 --- a/src/electron/renderer/src/stories/List.stories.js +++ b/stories/components/List.stories.js @@ -1,4 +1,4 @@ -import { List } from "./List"; +import { List } from "../../src/electron/renderer/src/stories/List"; export default { title: "Components/List", diff --git a/src/electron/renderer/src/stories/pages/guided-mode/Locate.stories.js b/stories/components/Locate.stories.js similarity index 90% rename from src/electron/renderer/src/stories/pages/guided-mode/Locate.stories.js rename to stories/components/Locate.stories.js index 1cd557b56..d86f73569 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/Locate.stories.js +++ b/stories/components/Locate.stories.js @@ -1,4 +1,4 @@ -import { globalState, PageTemplate } from "./storyStates"; +import { globalState, PageTemplate } from "../pages/storyStates"; export default { title: "Pages/Guided Mode/Locate", diff --git a/src/electron/renderer/src/stories/multiselect/Multiselect.stories.js b/stories/components/Multiselect.stories.js similarity index 87% rename from src/electron/renderer/src/stories/multiselect/Multiselect.stories.js rename to stories/components/Multiselect.stories.js index 46b0ab35b..083afab17 100644 --- a/src/electron/renderer/src/stories/multiselect/Multiselect.stories.js +++ b/stories/components/Multiselect.stories.js @@ -1,4 +1,4 @@ -import { MultiSelectForm } from "./MultiSelectForm.js"; +import { MultiSelectForm } from "../../src/electron/renderer/src/stories/multiselect/MultiSelectForm.js"; export default { title: "Components/Multiselect Form", diff --git a/src/electron/renderer/src/stories/OptionalSection.stories.js b/stories/components/OptionalSection.stories.js similarity index 77% rename from src/electron/renderer/src/stories/OptionalSection.stories.js rename to stories/components/OptionalSection.stories.js index 7cea5356b..f0c3751d4 100644 --- a/src/electron/renderer/src/stories/OptionalSection.stories.js +++ b/stories/components/OptionalSection.stories.js @@ -1,4 +1,4 @@ -import { OptionalSection } from "./OptionalSection"; +import { OptionalSection } from "../../src/electron/renderer/src/stories/OptionalSection"; export default { title: "Components/Optional Section", diff --git a/src/electron/renderer/src/stories/ProgressBar.stories.js b/stories/components/ProgressBar.stories.js similarity index 87% rename from src/electron/renderer/src/stories/ProgressBar.stories.js rename to stories/components/ProgressBar.stories.js index 78da06910..f5b9ab929 100644 --- a/src/electron/renderer/src/stories/ProgressBar.stories.js +++ b/stories/components/ProgressBar.stories.js @@ -1,4 +1,4 @@ -import { ProgressBar } from "./ProgressBar"; +import { ProgressBar } from "../../src/electron/renderer/src/stories/ProgressBar"; export default { title: "Components/Progress Bar", diff --git a/src/electron/renderer/src/stories/Search.stories.js b/stories/components/Search.stories.js similarity index 94% rename from src/electron/renderer/src/stories/Search.stories.js rename to stories/components/Search.stories.js index b78b16f37..a5c200017 100644 --- a/src/electron/renderer/src/stories/Search.stories.js +++ b/stories/components/Search.stories.js @@ -1,4 +1,4 @@ -import { Search } from "./Search"; +import { Search } from "../../src/electron/renderer/src/stories/Search"; // More on how to set up stories at: https://storybook.js.org/docs/7.0/web-components/writing-stories/introduction export default { diff --git a/src/electron/renderer/src/stories/status/StatusBar.stories.js b/stories/components/StatusBar.stories.js similarity index 63% rename from src/electron/renderer/src/stories/status/StatusBar.stories.js rename to stories/components/StatusBar.stories.js index 1188ca171..342325e29 100644 --- a/src/electron/renderer/src/stories/status/StatusBar.stories.js +++ b/stories/components/StatusBar.stories.js @@ -1,8 +1,8 @@ -import { StatusBar } from "./StatusBar"; +import { StatusBar } from "../../src/electron/renderer/src/stories/status/StatusBar"; import { unsafeSVG } from "lit/directives/unsafe-svg.js"; -import pythonSVG from "../assets/python.svg?raw"; -import webAssetSVG from "../assets/web_asset.svg?raw"; -import wifiSVG from "../assets/wifi.svg?raw"; +import pythonSVG from "../../src/electron/renderer/assets/icons/python.svg?raw"; +import webAssetSVG from "../../src/electron/renderer/assets/icons/web_asset.svg?raw"; +import wifiSVG from "../../src/electron/renderer/assets/icons/wifi.svg?raw"; export default { title: "Components/Status Bar", diff --git a/src/electron/renderer/src/stories/Table.stories.js b/stories/components/Table.stories.js similarity index 82% rename from src/electron/renderer/src/stories/Table.stories.js rename to stories/components/Table.stories.js index 14573a5ab..e4272dee4 100644 --- a/src/electron/renderer/src/stories/Table.stories.js +++ b/stories/components/Table.stories.js @@ -1,8 +1,8 @@ -import { Table } from "./Table.js"; +import { Table } from "../../src/electron/renderer/src/stories/Table.js"; -import getSubjectSchema from "../../../../schemas/subject.schema"; -import { SimpleTable } from "./SimpleTable.js"; -import { BasicTable } from "./BasicTable.js"; +import getSubjectSchema from "../../src/schemas/subject.schema"; +import { SimpleTable } from "../../src/electron/renderer/src/stories/SimpleTable.js"; +import { BasicTable } from "../../src/electron/renderer/src/stories/BasicTable.js"; export default { title: "Components/Table", diff --git a/src/electron/renderer/src/stories/preview/inspector/test.json b/stories/inputs/inspector_output.json similarity index 100% rename from src/electron/renderer/src/stories/preview/inspector/test.json rename to stories/inputs/inspector_output.json diff --git a/src/schemas/json/generated/AlphaOmegaRecordingInterface.json b/stories/inputs/interface_schemas/AlphaOmegaRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/AlphaOmegaRecordingInterface.json rename to stories/inputs/interface_schemas/AlphaOmegaRecordingInterface.json diff --git a/src/schemas/json/generated/AudioInterface.json b/stories/inputs/interface_schemas/AudioInterface.json similarity index 100% rename from src/schemas/json/generated/AudioInterface.json rename to stories/inputs/interface_schemas/AudioInterface.json diff --git a/src/schemas/json/generated/AxonaRecordingInterface.json b/stories/inputs/interface_schemas/AxonaRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/AxonaRecordingInterface.json rename to stories/inputs/interface_schemas/AxonaRecordingInterface.json diff --git a/src/schemas/json/generated/BiocamRecordingInterface.json b/stories/inputs/interface_schemas/BiocamRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/BiocamRecordingInterface.json rename to stories/inputs/interface_schemas/BiocamRecordingInterface.json diff --git a/src/schemas/json/generated/BlackrockRecordingInterface.json b/stories/inputs/interface_schemas/BlackrockRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/BlackrockRecordingInterface.json rename to stories/inputs/interface_schemas/BlackrockRecordingInterface.json diff --git a/src/schemas/json/generated/BlackrockSortingInterface.json b/stories/inputs/interface_schemas/BlackrockSortingInterface.json similarity index 100% rename from src/schemas/json/generated/BlackrockSortingInterface.json rename to stories/inputs/interface_schemas/BlackrockSortingInterface.json diff --git a/src/schemas/json/generated/BrukerTiffMultiPlaneConverter.json b/stories/inputs/interface_schemas/BrukerTiffMultiPlaneConverter.json similarity index 100% rename from src/schemas/json/generated/BrukerTiffMultiPlaneConverter.json rename to stories/inputs/interface_schemas/BrukerTiffMultiPlaneConverter.json diff --git a/src/schemas/json/generated/BrukerTiffMultiPlaneImagingInterface.json b/stories/inputs/interface_schemas/BrukerTiffMultiPlaneImagingInterface.json similarity index 100% rename from src/schemas/json/generated/BrukerTiffMultiPlaneImagingInterface.json rename to stories/inputs/interface_schemas/BrukerTiffMultiPlaneImagingInterface.json diff --git a/src/schemas/json/generated/BrukerTiffSinglePlaneConverter.json b/stories/inputs/interface_schemas/BrukerTiffSinglePlaneConverter.json similarity index 100% rename from src/schemas/json/generated/BrukerTiffSinglePlaneConverter.json rename to stories/inputs/interface_schemas/BrukerTiffSinglePlaneConverter.json diff --git a/src/schemas/json/generated/BrukerTiffSinglePlaneImagingInterface.json b/stories/inputs/interface_schemas/BrukerTiffSinglePlaneImagingInterface.json similarity index 100% rename from src/schemas/json/generated/BrukerTiffSinglePlaneImagingInterface.json rename to stories/inputs/interface_schemas/BrukerTiffSinglePlaneImagingInterface.json diff --git a/src/schemas/json/generated/CaimanSegmentationInterface.json b/stories/inputs/interface_schemas/CaimanSegmentationInterface.json similarity index 100% rename from src/schemas/json/generated/CaimanSegmentationInterface.json rename to stories/inputs/interface_schemas/CaimanSegmentationInterface.json diff --git a/src/schemas/json/generated/CellExplorerRecordingInterface.json b/stories/inputs/interface_schemas/CellExplorerRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/CellExplorerRecordingInterface.json rename to stories/inputs/interface_schemas/CellExplorerRecordingInterface.json diff --git a/src/schemas/json/generated/CellExplorerSortingInterface.json b/stories/inputs/interface_schemas/CellExplorerSortingInterface.json similarity index 100% rename from src/schemas/json/generated/CellExplorerSortingInterface.json rename to stories/inputs/interface_schemas/CellExplorerSortingInterface.json diff --git a/src/schemas/json/generated/CnmfeSegmentationInterface.json b/stories/inputs/interface_schemas/CnmfeSegmentationInterface.json similarity index 100% rename from src/schemas/json/generated/CnmfeSegmentationInterface.json rename to stories/inputs/interface_schemas/CnmfeSegmentationInterface.json diff --git a/src/schemas/json/generated/DeepLabCutInterface.json b/stories/inputs/interface_schemas/DeepLabCutInterface.json similarity index 100% rename from src/schemas/json/generated/DeepLabCutInterface.json rename to stories/inputs/interface_schemas/DeepLabCutInterface.json diff --git a/src/schemas/json/generated/EDFRecordingInterface.json b/stories/inputs/interface_schemas/EDFRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/EDFRecordingInterface.json rename to stories/inputs/interface_schemas/EDFRecordingInterface.json diff --git a/src/schemas/json/generated/ExtractSegmentationInterface.json b/stories/inputs/interface_schemas/ExtractSegmentationInterface.json similarity index 100% rename from src/schemas/json/generated/ExtractSegmentationInterface.json rename to stories/inputs/interface_schemas/ExtractSegmentationInterface.json diff --git a/src/schemas/json/generated/FicTracDataInterface.json b/stories/inputs/interface_schemas/FicTracDataInterface.json similarity index 100% rename from src/schemas/json/generated/FicTracDataInterface.json rename to stories/inputs/interface_schemas/FicTracDataInterface.json diff --git a/src/schemas/json/generated/IntanRecordingInterface.json b/stories/inputs/interface_schemas/IntanRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/IntanRecordingInterface.json rename to stories/inputs/interface_schemas/IntanRecordingInterface.json diff --git a/src/schemas/json/generated/KiloSortSortingInterface.json b/stories/inputs/interface_schemas/KiloSortSortingInterface.json similarity index 100% rename from src/schemas/json/generated/KiloSortSortingInterface.json rename to stories/inputs/interface_schemas/KiloSortSortingInterface.json diff --git a/src/schemas/json/generated/MCSRawRecordingInterface.json b/stories/inputs/interface_schemas/MCSRawRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/MCSRawRecordingInterface.json rename to stories/inputs/interface_schemas/MCSRawRecordingInterface.json diff --git a/src/schemas/json/generated/MEArecRecordingInterface.json b/stories/inputs/interface_schemas/MEArecRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/MEArecRecordingInterface.json rename to stories/inputs/interface_schemas/MEArecRecordingInterface.json diff --git a/src/schemas/json/generated/MicroManagerTiffImagingInterface.json b/stories/inputs/interface_schemas/MicroManagerTiffImagingInterface.json similarity index 100% rename from src/schemas/json/generated/MicroManagerTiffImagingInterface.json rename to stories/inputs/interface_schemas/MicroManagerTiffImagingInterface.json diff --git a/src/schemas/json/generated/MiniscopeBehaviorInterface.json b/stories/inputs/interface_schemas/MiniscopeBehaviorInterface.json similarity index 100% rename from src/schemas/json/generated/MiniscopeBehaviorInterface.json rename to stories/inputs/interface_schemas/MiniscopeBehaviorInterface.json diff --git a/src/schemas/json/generated/MiniscopeConverter.json b/stories/inputs/interface_schemas/MiniscopeConverter.json similarity index 100% rename from src/schemas/json/generated/MiniscopeConverter.json rename to stories/inputs/interface_schemas/MiniscopeConverter.json diff --git a/src/schemas/json/generated/MiniscopeImagingInterface.json b/stories/inputs/interface_schemas/MiniscopeImagingInterface.json similarity index 100% rename from src/schemas/json/generated/MiniscopeImagingInterface.json rename to stories/inputs/interface_schemas/MiniscopeImagingInterface.json diff --git a/src/schemas/json/generated/NeuralynxRecordingInterface.json b/stories/inputs/interface_schemas/NeuralynxRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/NeuralynxRecordingInterface.json rename to stories/inputs/interface_schemas/NeuralynxRecordingInterface.json diff --git a/src/schemas/json/generated/NeuroScopeLFPInterface.json b/stories/inputs/interface_schemas/NeuroScopeLFPInterface.json similarity index 100% rename from src/schemas/json/generated/NeuroScopeLFPInterface.json rename to stories/inputs/interface_schemas/NeuroScopeLFPInterface.json diff --git a/src/schemas/json/generated/NeuroScopeRecordingInterface.json b/stories/inputs/interface_schemas/NeuroScopeRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/NeuroScopeRecordingInterface.json rename to stories/inputs/interface_schemas/NeuroScopeRecordingInterface.json diff --git a/src/schemas/json/generated/NeuroScopeSortingInterface.json b/stories/inputs/interface_schemas/NeuroScopeSortingInterface.json similarity index 100% rename from src/schemas/json/generated/NeuroScopeSortingInterface.json rename to stories/inputs/interface_schemas/NeuroScopeSortingInterface.json diff --git a/src/schemas/json/generated/OpenEphysRecordingInterface.json b/stories/inputs/interface_schemas/OpenEphysRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/OpenEphysRecordingInterface.json rename to stories/inputs/interface_schemas/OpenEphysRecordingInterface.json diff --git a/src/schemas/json/generated/PhySortingInterface.json b/stories/inputs/interface_schemas/PhySortingInterface.json similarity index 100% rename from src/schemas/json/generated/PhySortingInterface.json rename to stories/inputs/interface_schemas/PhySortingInterface.json diff --git a/src/schemas/json/generated/PlexonRecordingInterface.json b/stories/inputs/interface_schemas/PlexonRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/PlexonRecordingInterface.json rename to stories/inputs/interface_schemas/PlexonRecordingInterface.json diff --git a/src/schemas/json/generated/PlexonSortingInterface.json b/stories/inputs/interface_schemas/PlexonSortingInterface.json similarity index 100% rename from src/schemas/json/generated/PlexonSortingInterface.json rename to stories/inputs/interface_schemas/PlexonSortingInterface.json diff --git a/src/schemas/json/generated/SLEAPInterface.json b/stories/inputs/interface_schemas/SLEAPInterface.json similarity index 100% rename from src/schemas/json/generated/SLEAPInterface.json rename to stories/inputs/interface_schemas/SLEAPInterface.json diff --git a/src/schemas/json/generated/SbxImagingInterface.json b/stories/inputs/interface_schemas/SbxImagingInterface.json similarity index 100% rename from src/schemas/json/generated/SbxImagingInterface.json rename to stories/inputs/interface_schemas/SbxImagingInterface.json diff --git a/src/schemas/json/generated/ScanImageImagingInterface.json b/stories/inputs/interface_schemas/ScanImageImagingInterface.json similarity index 100% rename from src/schemas/json/generated/ScanImageImagingInterface.json rename to stories/inputs/interface_schemas/ScanImageImagingInterface.json diff --git a/src/schemas/json/generated/Spike2RecordingInterface.json b/stories/inputs/interface_schemas/Spike2RecordingInterface.json similarity index 100% rename from src/schemas/json/generated/Spike2RecordingInterface.json rename to stories/inputs/interface_schemas/Spike2RecordingInterface.json diff --git a/src/schemas/json/generated/SpikeGLXConverterPipe.json b/stories/inputs/interface_schemas/SpikeGLXConverterPipe.json similarity index 100% rename from src/schemas/json/generated/SpikeGLXConverterPipe.json rename to stories/inputs/interface_schemas/SpikeGLXConverterPipe.json diff --git a/src/schemas/json/generated/SpikeGLXNIDQInterface.json b/stories/inputs/interface_schemas/SpikeGLXNIDQInterface.json similarity index 100% rename from src/schemas/json/generated/SpikeGLXNIDQInterface.json rename to stories/inputs/interface_schemas/SpikeGLXNIDQInterface.json diff --git a/src/schemas/json/generated/SpikeGLXRecordingInterface.json b/stories/inputs/interface_schemas/SpikeGLXRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/SpikeGLXRecordingInterface.json rename to stories/inputs/interface_schemas/SpikeGLXRecordingInterface.json diff --git a/src/schemas/json/generated/Suite2pSegmentationInterface.json b/stories/inputs/interface_schemas/Suite2pSegmentationInterface.json similarity index 100% rename from src/schemas/json/generated/Suite2pSegmentationInterface.json rename to stories/inputs/interface_schemas/Suite2pSegmentationInterface.json diff --git a/src/schemas/json/generated/TdtRecordingInterface.json b/stories/inputs/interface_schemas/TdtRecordingInterface.json similarity index 100% rename from src/schemas/json/generated/TdtRecordingInterface.json rename to stories/inputs/interface_schemas/TdtRecordingInterface.json diff --git a/src/schemas/json/generated/TiffImagingInterface.json b/stories/inputs/interface_schemas/TiffImagingInterface.json similarity index 100% rename from src/schemas/json/generated/TiffImagingInterface.json rename to stories/inputs/interface_schemas/TiffImagingInterface.json diff --git a/src/schemas/json/generated/VideoInterface.json b/stories/inputs/interface_schemas/VideoInterface.json similarity index 100% rename from src/schemas/json/generated/VideoInterface.json rename to stories/inputs/interface_schemas/VideoInterface.json diff --git a/src/electron/renderer/src/stories/pages/guided-mode/GuidedHome.stories.js b/stories/pages/GuidedHome.stories.js similarity index 100% rename from src/electron/renderer/src/stories/pages/guided-mode/GuidedHome.stories.js rename to stories/pages/GuidedHome.stories.js diff --git a/src/electron/renderer/src/stories/pages/guided-mode/Metadata.stories.js b/stories/pages/Metadata.stories.js similarity index 100% rename from src/electron/renderer/src/stories/pages/guided-mode/Metadata.stories.js rename to stories/pages/Metadata.stories.js diff --git a/src/electron/renderer/src/stories/pages/guided-mode/NewDataset.stories.js b/stories/pages/NewDataset.stories.js similarity index 100% rename from src/electron/renderer/src/stories/pages/guided-mode/NewDataset.stories.js rename to stories/pages/NewDataset.stories.js diff --git a/src/electron/renderer/src/stories/pages/guided-mode/Preview.stories.js b/stories/pages/Preview.stories.js similarity index 100% rename from src/electron/renderer/src/stories/pages/guided-mode/Preview.stories.js rename to stories/pages/Preview.stories.js diff --git a/src/electron/renderer/src/stories/pages/guided-mode/Review.stories.js b/stories/pages/Review.stories.js similarity index 100% rename from src/electron/renderer/src/stories/pages/guided-mode/Review.stories.js rename to stories/pages/Review.stories.js diff --git a/src/electron/renderer/src/stories/pages/guided-mode/SourceData.stories.js b/stories/pages/SourceData.stories.js similarity index 83% rename from src/electron/renderer/src/stories/pages/guided-mode/SourceData.stories.js rename to stories/pages/SourceData.stories.js index 348d1fc09..dc4daf3e3 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/SourceData.stories.js +++ b/stories/pages/SourceData.stories.js @@ -1,49 +1,49 @@ import { globalState, PageTemplate } from "./storyStates"; -import SpikeGLXRecordingInterfaceSchema from "../../../../../../schemas/json/generated/SpikeGLXRecordingInterface.json"; -import SpikeGLXNIDQInterfaceSchema from "../../../../../../schemas/json/generated/SpikeGLXNIDQInterface.json"; -import PhySortingInterfaceSchema from "../../../../../../schemas/json/generated/PhySortingInterface.json"; -import NeuroScopeRecordingInterfaceSchema from "../../../../../../schemas/json/generated/NeuroScopeRecordingInterface.json"; -import NeuroScopeLFPInterfaceSchema from "../../../../../../schemas/json/generated/NeuroScopeLFPInterface.json"; -import NeuroScopeSortingInterfaceSchema from "../../../../../../schemas/json/generated/NeuroScopeSortingInterface.json"; -import BiocamRecordingInterfaceSchema from "../../../../../../schemas/json/generated/BiocamRecordingInterface.json"; -import IntanRecordingInterfaceSchema from "../../../../../../schemas/json/generated/IntanRecordingInterface.json"; -import OpenEphysRecordingInterfaceSchema from "../../../../../../schemas/json/generated/OpenEphysRecordingInterface.json"; -import BlackrockRecordingInterfaceSchema from "../../../../../../schemas/json/generated/BlackrockRecordingInterface.json"; -import BlackrockSortingInterfaceSchema from "../../../../../../schemas/json/generated/BlackrockSortingInterface.json"; -import CellExplorerSortingInterfaceSchema from "../../../../../../schemas/json/generated/CellExplorerSortingInterface.json"; -import KiloSortSortingInterfaceSchema from "../../../../../../schemas/json/generated/KiloSortSortingInterface.json"; -import TdtRecordingInterfaceSchema from "../../../../../../schemas/json/generated/TdtRecordingInterface.json"; -import Spike2RecordingInterfaceSchema from "../../../../../../schemas/json/generated/Spike2RecordingInterface.json"; -import BrukerTiffSinglePlaneImagingInterfaceSchema from "../../../../../../schemas/json/generated/BrukerTiffSinglePlaneImagingInterface.json"; -import ExtractSegmentationInterfaceSchema from "../../../../../../schemas/json/generated/ExtractSegmentationInterface.json"; -import CnmfeSegmentationInterfaceSchema from "../../../../../../schemas/json/generated/CnmfeSegmentationInterface.json"; -import BrukerTiffMultiPlaneImagingInterfaceSchema from "../../../../../../schemas/json/generated/BrukerTiffMultiPlaneImagingInterface.json"; -import MicroManagerTiffImagingInterfaceSchema from "../../../../../../schemas/json/generated/MicroManagerTiffImagingInterface.json"; -import ScanImageImagingInterfaceSchema from "../../../../../../schemas/json/generated/ScanImageImagingInterface.json"; -import TiffImagingInterfaceSchema from "../../../../../../schemas/json/generated/TiffImagingInterface.json"; -import MiniscopeImagingInterfaceSchema from "../../../../../../schemas/json/generated/MiniscopeImagingInterface.json"; -import SbxImagingInterfaceSchema from "../../../../../../schemas/json/generated/SbxImagingInterface.json"; -import CaimanSegmentationInterfaceSchema from "../../../../../../schemas/json/generated/CaimanSegmentationInterface.json"; -import MCSRawRecordingInterfaceSchema from "../../../../../../schemas/json/generated/MCSRawRecordingInterface.json"; -import MEArecRecordingInterfaceSchema from "../../../../../../schemas/json/generated/MEArecRecordingInterface.json"; -import PlexonRecordingInterfaceSchema from "../../../../../../schemas/json/generated/PlexonRecordingInterface.json"; -import PlexonSortingInterfaceSchema from "../../../../../../schemas/json/generated/PlexonSortingInterface.json"; -import AxonaRecordingInterfaceSchema from "../../../../../../schemas/json/generated/AxonaRecordingInterface.json"; -import VideoInterfaceSchema from "../../../../../../schemas/json/generated/VideoInterface.json"; -import NeuralynxRecordingInterfaceSchema from "../../../../../../schemas/json/generated/NeuralynxRecordingInterface.json"; -import Suite2pSegmentationInterfaceSchema from "../../../../../../schemas/json/generated/Suite2pSegmentationInterface.json"; -import AlphaOmegaRecordingInterfaceSchema from "../../../../../../schemas/json/generated/AlphaOmegaRecordingInterface.json"; -import DeepLabCutInterfaceSchema from "../../../../../../schemas/json/generated/DeepLabCutInterface.json"; -import SLEAPInterfaceSchema from "../../../../../../schemas/json/generated/SLEAPInterface.json"; -import FicTracDataInterfaceSchema from "../../../../../../schemas/json/generated/FicTracDataInterface.json"; -import AudioInterfaceSchema from "../../../../../../schemas/json/generated/AudioInterface.json"; -import MiniscopeBehaviorInterfaceSchema from "../../../../../../schemas/json/generated/MiniscopeBehaviorInterface.json"; -import EDFRecordingInterfaceSchema from "../../../../../../schemas/json/generated/EDFRecordingInterface.json"; -import SpikeGLXConverterPipeSchema from "../../../../../../schemas/json/generated/SpikeGLXConverterPipe.json"; -import BrukerTiffSinglePlaneConverterSchema from "../../../../../../schemas/json/generated/BrukerTiffSinglePlaneConverter.json"; -import BrukerTiffMultiPlaneConverterSchema from "../../../../../../schemas/json/generated/BrukerTiffMultiPlaneConverter.json"; -import MiniscopeConverterSchema from "../../../../../../schemas/json/generated/MiniscopeConverter.json"; -import CellExplorerRecordingInterfaceSchema from "../../../../../../schemas/json/generated/CellExplorerRecordingInterface.json"; +import SpikeGLXRecordingInterfaceSchema from "../inputs/interface_schemas/SpikeGLXRecordingInterface.json"; +import SpikeGLXNIDQInterfaceSchema from "../inputs/interface_schemas/SpikeGLXNIDQInterface.json"; +import PhySortingInterfaceSchema from "../inputs/interface_schemas/PhySortingInterface.json"; +import NeuroScopeRecordingInterfaceSchema from "../inputs/interface_schemas/NeuroScopeRecordingInterface.json"; +import NeuroScopeLFPInterfaceSchema from "../inputs/interface_schemas/NeuroScopeLFPInterface.json"; +import NeuroScopeSortingInterfaceSchema from "../inputs/interface_schemas/NeuroScopeSortingInterface.json"; +import BiocamRecordingInterfaceSchema from "../inputs/interface_schemas/BiocamRecordingInterface.json"; +import IntanRecordingInterfaceSchema from "../inputs/interface_schemas/IntanRecordingInterface.json"; +import OpenEphysRecordingInterfaceSchema from "../inputs/interface_schemas/OpenEphysRecordingInterface.json"; +import BlackrockRecordingInterfaceSchema from "../inputs/interface_schemas/BlackrockRecordingInterface.json"; +import BlackrockSortingInterfaceSchema from "../inputs/interface_schemas/BlackrockSortingInterface.json"; +import CellExplorerSortingInterfaceSchema from "../inputs/interface_schemas/CellExplorerSortingInterface.json"; +import KiloSortSortingInterfaceSchema from "../inputs/interface_schemas/KiloSortSortingInterface.json"; +import TdtRecordingInterfaceSchema from "../inputs/interface_schemas/TdtRecordingInterface.json"; +import Spike2RecordingInterfaceSchema from "../inputs/interface_schemas/Spike2RecordingInterface.json"; +import BrukerTiffSinglePlaneImagingInterfaceSchema from "../inputs/interface_schemas/BrukerTiffSinglePlaneImagingInterface.json"; +import ExtractSegmentationInterfaceSchema from "../inputs/interface_schemas/ExtractSegmentationInterface.json"; +import CnmfeSegmentationInterfaceSchema from "../inputs/interface_schemas/CnmfeSegmentationInterface.json"; +import BrukerTiffMultiPlaneImagingInterfaceSchema from "../inputs/interface_schemas/BrukerTiffMultiPlaneImagingInterface.json"; +import MicroManagerTiffImagingInterfaceSchema from "../inputs/interface_schemas/MicroManagerTiffImagingInterface.json"; +import ScanImageImagingInterfaceSchema from "../inputs/interface_schemas/ScanImageImagingInterface.json"; +import TiffImagingInterfaceSchema from "../inputs/interface_schemas/TiffImagingInterface.json"; +import MiniscopeImagingInterfaceSchema from "../inputs/interface_schemas/MiniscopeImagingInterface.json"; +import SbxImagingInterfaceSchema from "../inputs/interface_schemas/SbxImagingInterface.json"; +import CaimanSegmentationInterfaceSchema from "../inputs/interface_schemas/CaimanSegmentationInterface.json"; +import MCSRawRecordingInterfaceSchema from "../inputs/interface_schemas/MCSRawRecordingInterface.json"; +import MEArecRecordingInterfaceSchema from "../inputs/interface_schemas/MEArecRecordingInterface.json"; +import PlexonRecordingInterfaceSchema from "../inputs/interface_schemas/PlexonRecordingInterface.json"; +import PlexonSortingInterfaceSchema from "../inputs/interface_schemas/PlexonSortingInterface.json"; +import AxonaRecordingInterfaceSchema from "../inputs/interface_schemas/AxonaRecordingInterface.json"; +import VideoInterfaceSchema from "../inputs/interface_schemas/VideoInterface.json"; +import NeuralynxRecordingInterfaceSchema from "../inputs/interface_schemas/NeuralynxRecordingInterface.json"; +import Suite2pSegmentationInterfaceSchema from "../inputs/interface_schemas/Suite2pSegmentationInterface.json"; +import AlphaOmegaRecordingInterfaceSchema from "../inputs/interface_schemas/AlphaOmegaRecordingInterface.json"; +import DeepLabCutInterfaceSchema from "../inputs/interface_schemas/DeepLabCutInterface.json"; +import SLEAPInterfaceSchema from "../inputs/interface_schemas/SLEAPInterface.json"; +import FicTracDataInterfaceSchema from "../inputs/interface_schemas/FicTracDataInterface.json"; +import AudioInterfaceSchema from "../inputs/interface_schemas/AudioInterface.json"; +import MiniscopeBehaviorInterfaceSchema from "../inputs/interface_schemas/MiniscopeBehaviorInterface.json"; +import EDFRecordingInterfaceSchema from "../inputs/interface_schemas/EDFRecordingInterface.json"; +import SpikeGLXConverterPipeSchema from "../inputs/interface_schemas/SpikeGLXConverterPipe.json"; +import BrukerTiffSinglePlaneConverterSchema from "../inputs/interface_schemas/BrukerTiffSinglePlaneConverter.json"; +import BrukerTiffMultiPlaneConverterSchema from "../inputs/interface_schemas/BrukerTiffMultiPlaneConverter.json"; +import MiniscopeConverterSchema from "../inputs/interface_schemas/MiniscopeConverter.json"; +import CellExplorerRecordingInterfaceSchema from "../inputs/interface_schemas/CellExplorerRecordingInterface.json"; export default { title: "Pages/Guided Mode/Source Data", diff --git a/src/electron/renderer/src/stories/pages/guided-mode/Structure.stories.js b/stories/pages/Structure.stories.js similarity index 100% rename from src/electron/renderer/src/stories/pages/guided-mode/Structure.stories.js rename to stories/pages/Structure.stories.js diff --git a/src/electron/renderer/src/stories/pages/guided-mode/Subjects.stories.js b/stories/pages/Subjects.stories.js similarity index 100% rename from src/electron/renderer/src/stories/pages/guided-mode/Subjects.stories.js rename to stories/pages/Subjects.stories.js diff --git a/src/electron/renderer/src/stories/pages/guided-mode/Upload.stories.js b/stories/pages/Upload.stories.js similarity index 100% rename from src/electron/renderer/src/stories/pages/guided-mode/Upload.stories.js rename to stories/pages/Upload.stories.js diff --git a/src/electron/renderer/src/stories/pages/guided-mode/storyStates.ts b/stories/pages/storyStates.ts similarity index 87% rename from src/electron/renderer/src/stories/pages/guided-mode/storyStates.ts rename to stories/pages/storyStates.ts index b8cd76034..8e518cfdf 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/storyStates.ts +++ b/stories/pages/storyStates.ts @@ -1,8 +1,8 @@ -import nwbBaseSchema from "../../../../../../schemas/base-metadata.schema.js"; +import nwbBaseSchema from "../../src/schemas/base-metadata.schema.js"; // import exephysExampleSchema from "../../../../../../schemas/json/ecephys_metadata_schema_example.json"; -import { dashboard } from "../../../pages.js"; -import { activateServer } from "../../../server/globals"; +import { dashboard } from "../../src/electron/renderer/src/pages.js"; +import { activateServer } from "../../src/electron/renderer/src/server/globals.js"; activateServer(); diff --git a/tsconfig.json b/tsconfig.json index a9f56c655..475adc385 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,6 @@ "noUnusedParameters": true, "noImplicitReturns": true }, - "include": ["src", "src/schemas", "frontend"], + "include": ["src", "src/schemas", "frontend", "stories/components/Table.stories.js", "stories/components/Search.stories.js", "stories/components/ProgressBar.stories.js", "stories/components/OptionalSection.stories.js", "stories/components/List.stories.js", "stories/components/JSONSchemaForm.stories.js", "stories/components/InstanceManager.stories.js", "stories/components/FileSystemSelector.stories.js", "stories/components/Button.stories.js", "stories/components/Accordion.stories.js", "stories/components/InspectorList.stories.js", "stories/components/StatusBar.stories.js", "stories/components/Multiselect.stories.js", "stories/Pages.stories.js", "stories/pages/GuidedHome.stories.js", "stories/components/Locate.stories.js", "stories/pages/Metadata.stories.js", "stories/pages/NewDataset.stories.js", "stories/pages/Preview.stories.js", "stories/pages/Review.stories.js", "stories/pages/SourceData.stories.js", "stories/pages/Structure.stories.js", "stories/pages/Subjects.stories.js", "stories/pages/Upload.stories.js", "stories/pages/storyStates.ts"], "exclude": ["node_modules"] } From d08c3f9680216e4d1cc00cbc3d90ea50d505f1f6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 21:58:46 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- generateInterfaceSchema.py | 4 +- src/electron/renderer/src/pages.js | 390 +++--- .../src/stories/FileSystemSelector.js | 610 ++++----- src/electron/renderer/src/stories/Search.js | 1048 +++++++------- .../pages/guided-mode/data/GuidedMetadata.js | 1200 ++++++++--------- .../guided-mode/data/GuidedPathExpansion.js | 898 ++++++------ .../guided-mode/data/GuidedSourceData.js | 768 +++++------ .../options/GuidedInspectorPage.js | 534 ++++---- .../guided-mode/options/GuidedStubPreview.js | 114 +- .../pages/guided-mode/options/GuidedUpload.js | 408 +++--- .../guided-mode/results/GuidedResults.js | 118 +- .../pages/guided-mode/setup/GuidedSubjects.js | 410 +++--- .../stories/pages/settings/SettingsPage.js | 690 +++++----- .../src/stories/pages/uploads/UploadsPage.js | 890 ++++++------ stories/Pages.stories.js | 72 +- stories/components/Accordion.stories.js | 84 +- stories/components/Button.stories.js | 98 +- .../components/FileSystemSelector.stories.js | 70 +- stories/components/InspectorList.stories.js | 26 +- stories/components/InstanceManager.stories.js | 162 +-- stories/components/JSONSchemaForm.stories.js | 256 ++-- stories/components/List.stories.js | 64 +- stories/components/Locate.stories.js | 58 +- stories/components/Multiselect.stories.js | 68 +- stories/components/OptionalSection.stories.js | 30 +- stories/components/ProgressBar.stories.js | 60 +- stories/components/Search.stories.js | 92 +- stories/components/StatusBar.stories.js | 64 +- stories/components/Table.stories.js | 136 +- stories/pages/SourceData.stories.js | 852 ++++++------ 30 files changed, 5138 insertions(+), 5136 deletions(-) diff --git a/generateInterfaceSchema.py b/generateInterfaceSchema.py index fe886ac86..b3e712f8a 100644 --- a/generateInterfaceSchema.py +++ b/generateInterfaceSchema.py @@ -31,7 +31,9 @@ class CustomNWBConverter(NWBConverter): sourceDataStoryPath = Path("stories/pages/SourceData.stories.js") -importCode = "\n".join(map(lambda arr: f"import {arr[0]}Schema from '../inputs/interface_schemas/{arr[1]}'", paths.items())) +importCode = "\n".join( + map(lambda arr: f"import {arr[0]}Schema from '../inputs/interface_schemas/{arr[1]}'", paths.items()) +) storyCode = "\n".join( map( lambda arr: f"""export const {arr[0]} = PageTemplate.bind({{}}); diff --git a/src/electron/renderer/src/pages.js b/src/electron/renderer/src/pages.js index d203b6512..0dc468955 100644 --- a/src/electron/renderer/src/pages.js +++ b/src/electron/renderer/src/pages.js @@ -1,195 +1,195 @@ -import { GettingStartedPage } from "./stories/pages/getting-started/GettingStarted"; -import { DocumentationPage } from "./stories/pages/documentation/Documentation"; -import { ContactPage } from "./stories/pages/contact-us/Contact"; -import { GuidedHomePage } from "./stories/pages/guided-mode/GuidedHome"; -import { GuidedNewDatasetPage } from "./stories/pages/guided-mode/setup/GuidedNewDatasetInfo"; -import { GuidedStructurePage } from "./stories/pages/guided-mode/data/GuidedStructure"; -import { sections } from "./stories/pages/globals"; -import { GuidedSubjectsPage } from "./stories/pages/guided-mode/setup/GuidedSubjects"; -import { GuidedSourceDataPage } from "./stories/pages/guided-mode/data/GuidedSourceData"; -import { GuidedMetadataPage } from "./stories/pages/guided-mode/data/GuidedMetadata"; -import { GuidedUploadPage } from "./stories/pages/guided-mode/options/GuidedUpload"; -import { GuidedResultsPage } from "./stories/pages/guided-mode/results/GuidedResults"; -import { Dashboard } from "./stories/Dashboard"; -import { GuidedStubPreviewPage } from "./stories/pages/guided-mode/options/GuidedStubPreview"; -import { GuidedInspectorPage } from "./stories/pages/guided-mode/options/GuidedInspectorPage"; - -import logo from "../assets/img/logo-guide-draft-transparent-tight.png"; -import { GuidedPathExpansionPage } from "./stories/pages/guided-mode/data/GuidedPathExpansion"; -import uploadIcon from "../assets/icons/dandi.svg"; -import inspectIcon from "../assets/icons/inspect.svg?raw"; -import neurosiftIcon from "../assets/icons/neurosift-logo.svg?raw"; - -import settingsIcon from "../assets/icons/settings.svg?raw"; - -import { UploadsPage } from "./stories/pages/uploads/UploadsPage"; -import { SettingsPage } from "./stories/pages/settings/SettingsPage"; -import { InspectPage } from "./stories/pages/inspect/InspectPage"; -import { PreviewPage } from "./stories/pages/preview/PreviewPage"; -import { GuidedPreform } from "./stories/pages/guided-mode/setup/Preform"; -import { GuidedDandiResultsPage } from "./stories/pages/guided-mode/results/GuidedDandiResults"; - -let dashboard = document.querySelector("nwb-dashboard"); -if (!dashboard) dashboard = new Dashboard(); -dashboard.logo = logo; -dashboard.name = "NWB GUIDE"; -dashboard.renderNameInSidebar = false; - -const resourcesGroup = "Resources"; - -const guidedIcon = ` - - - - -`; - -const documentationIcon = ` - - -`; - -const contactIcon = ` - -`; - -const pages = { - "/": new GuidedHomePage({ - label: "Convert", - icon: guidedIcon, - pages: { - details: new GuidedNewDatasetPage({ - title: "Project Setup", - label: "Project details", - section: sections[0], - }), - - workflow: new GuidedPreform({ - title: "Pipeline Workflow", - label: "Pipeline workflow", - section: sections[0], - }), - - structure: new GuidedStructurePage({ - title: "Provide Data Formats", - label: "Data formats", - section: sections[0], - }), - - locate: new GuidedPathExpansionPage({ - title: "Locate Data", - label: "Locate data", - section: sections[0], - }), - - subjects: new GuidedSubjectsPage({ - title: "Subject Metadata", - label: "Subject details", - section: sections[0], - }), - - sourcedata: new GuidedSourceDataPage({ - title: "Source Data Information", - label: "Source data", - section: sections[1], - }), - - metadata: new GuidedMetadataPage({ - title: "File Metadata", - label: "File metadata", - section: sections[1], - }), - - inspect: new GuidedInspectorPage({ - title: "Inspector Report", - label: "Validate metadata", - section: sections[2], - sync: ["preview"], - }), - - preview: new GuidedStubPreviewPage({ - title: "Conversion Preview", - label: "Preview NWB files", - section: sections[2], - sync: ["preview"], - }), - - conversion: new GuidedResultsPage({ - title: "Conversion Review", - label: "Review conversion", - section: sections[2], - sync: ["conversion"], - }), - - upload: new GuidedUploadPage({ - title: "DANDI Upload", - label: "Upload to DANDI", - section: sections[3], - sync: ["conversion"], - }), - - review: new GuidedDandiResultsPage({ - title: "Upload Review", - label: "Review published data", - section: sections[3], - }), - }, - }), - validate: new InspectPage({ - label: "Validate", - icon: inspectIcon, - }), - explore: new PreviewPage({ - label: "Explore", - icon: neurosiftIcon, - }), - uploads: new UploadsPage({ - label: "Upload", - icon: uploadIcon, - }), - docs: new DocumentationPage({ - label: "Documentation", - icon: documentationIcon, - group: resourcesGroup, - }), - contact: new ContactPage({ - label: "Contact Us", - icon: contactIcon, - group: resourcesGroup, - }), - settings: new SettingsPage({ - label: "Settings", - icon: settingsIcon, - group: "bottom", - }), -}; - -dashboard.pages = pages; - -export { dashboard }; +import { GettingStartedPage } from "./stories/pages/getting-started/GettingStarted"; +import { DocumentationPage } from "./stories/pages/documentation/Documentation"; +import { ContactPage } from "./stories/pages/contact-us/Contact"; +import { GuidedHomePage } from "./stories/pages/guided-mode/GuidedHome"; +import { GuidedNewDatasetPage } from "./stories/pages/guided-mode/setup/GuidedNewDatasetInfo"; +import { GuidedStructurePage } from "./stories/pages/guided-mode/data/GuidedStructure"; +import { sections } from "./stories/pages/globals"; +import { GuidedSubjectsPage } from "./stories/pages/guided-mode/setup/GuidedSubjects"; +import { GuidedSourceDataPage } from "./stories/pages/guided-mode/data/GuidedSourceData"; +import { GuidedMetadataPage } from "./stories/pages/guided-mode/data/GuidedMetadata"; +import { GuidedUploadPage } from "./stories/pages/guided-mode/options/GuidedUpload"; +import { GuidedResultsPage } from "./stories/pages/guided-mode/results/GuidedResults"; +import { Dashboard } from "./stories/Dashboard"; +import { GuidedStubPreviewPage } from "./stories/pages/guided-mode/options/GuidedStubPreview"; +import { GuidedInspectorPage } from "./stories/pages/guided-mode/options/GuidedInspectorPage"; + +import logo from "../assets/img/logo-guide-draft-transparent-tight.png"; +import { GuidedPathExpansionPage } from "./stories/pages/guided-mode/data/GuidedPathExpansion"; +import uploadIcon from "../assets/icons/dandi.svg"; +import inspectIcon from "../assets/icons/inspect.svg?raw"; +import neurosiftIcon from "../assets/icons/neurosift-logo.svg?raw"; + +import settingsIcon from "../assets/icons/settings.svg?raw"; + +import { UploadsPage } from "./stories/pages/uploads/UploadsPage"; +import { SettingsPage } from "./stories/pages/settings/SettingsPage"; +import { InspectPage } from "./stories/pages/inspect/InspectPage"; +import { PreviewPage } from "./stories/pages/preview/PreviewPage"; +import { GuidedPreform } from "./stories/pages/guided-mode/setup/Preform"; +import { GuidedDandiResultsPage } from "./stories/pages/guided-mode/results/GuidedDandiResults"; + +let dashboard = document.querySelector("nwb-dashboard"); +if (!dashboard) dashboard = new Dashboard(); +dashboard.logo = logo; +dashboard.name = "NWB GUIDE"; +dashboard.renderNameInSidebar = false; + +const resourcesGroup = "Resources"; + +const guidedIcon = ` + + + + +`; + +const documentationIcon = ` + + +`; + +const contactIcon = ` + +`; + +const pages = { + "/": new GuidedHomePage({ + label: "Convert", + icon: guidedIcon, + pages: { + details: new GuidedNewDatasetPage({ + title: "Project Setup", + label: "Project details", + section: sections[0], + }), + + workflow: new GuidedPreform({ + title: "Pipeline Workflow", + label: "Pipeline workflow", + section: sections[0], + }), + + structure: new GuidedStructurePage({ + title: "Provide Data Formats", + label: "Data formats", + section: sections[0], + }), + + locate: new GuidedPathExpansionPage({ + title: "Locate Data", + label: "Locate data", + section: sections[0], + }), + + subjects: new GuidedSubjectsPage({ + title: "Subject Metadata", + label: "Subject details", + section: sections[0], + }), + + sourcedata: new GuidedSourceDataPage({ + title: "Source Data Information", + label: "Source data", + section: sections[1], + }), + + metadata: new GuidedMetadataPage({ + title: "File Metadata", + label: "File metadata", + section: sections[1], + }), + + inspect: new GuidedInspectorPage({ + title: "Inspector Report", + label: "Validate metadata", + section: sections[2], + sync: ["preview"], + }), + + preview: new GuidedStubPreviewPage({ + title: "Conversion Preview", + label: "Preview NWB files", + section: sections[2], + sync: ["preview"], + }), + + conversion: new GuidedResultsPage({ + title: "Conversion Review", + label: "Review conversion", + section: sections[2], + sync: ["conversion"], + }), + + upload: new GuidedUploadPage({ + title: "DANDI Upload", + label: "Upload to DANDI", + section: sections[3], + sync: ["conversion"], + }), + + review: new GuidedDandiResultsPage({ + title: "Upload Review", + label: "Review published data", + section: sections[3], + }), + }, + }), + validate: new InspectPage({ + label: "Validate", + icon: inspectIcon, + }), + explore: new PreviewPage({ + label: "Explore", + icon: neurosiftIcon, + }), + uploads: new UploadsPage({ + label: "Upload", + icon: uploadIcon, + }), + docs: new DocumentationPage({ + label: "Documentation", + icon: documentationIcon, + group: resourcesGroup, + }), + contact: new ContactPage({ + label: "Contact Us", + icon: contactIcon, + group: resourcesGroup, + }), + settings: new SettingsPage({ + label: "Settings", + icon: settingsIcon, + group: "bottom", + }), +}; + +dashboard.pages = pages; + +export { dashboard }; diff --git a/src/electron/renderer/src/stories/FileSystemSelector.js b/src/electron/renderer/src/stories/FileSystemSelector.js index 1a64ce91f..3bef33378 100644 --- a/src/electron/renderer/src/stories/FileSystemSelector.js +++ b/src/electron/renderer/src/stories/FileSystemSelector.js @@ -1,305 +1,305 @@ -import { LitElement, css, html } from "lit"; - -import { fs, remote } from "../electron/index"; -import { List } from "./List"; -const { dialog } = remote; - -import restartSVG from "../../assets/icons/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 - .map((type) => getObjectTypeReferenceString(type, multiple, { native, nested: true })) - .join(" / ")}`; - - const isDir = type === "directory"; - return multiple && (!isDir || (isDir && !native) || dialog) - ? type === "directory" - ? "directories" - : "files" - : nested - ? type - : `a ${type}`; -} - -const componentCSS = css` - * { - box-sizing: border-box; - } - - :host { - display: inline-block; - position: relative; - width: 100%; - } - - :host([manytypes="true"]) > button { - cursor: default; - } - - nwb-list { - width: 100px; - } - - #button-div { - margin-top: 10px; - display: flex; - gap: 5px; - } - - #button-div > nwb-button { - margin-bottom: 10px; - } - - button { - background: WhiteSmoke; - border: 1px solid #c3c3c3; - border-radius: 4px; - padding: 25px; - width: 100%; - color: dimgray; - cursor: pointer; - overflow-wrap: break-word; - text-align: center; - transition: background 0.5s; - } - - small { - color: silver; - } - - :host(.active) button { - background: rgb(240, 240, 240); - } - - .controls { - position: absolute; - right: 0; - bottom: 0; - cursor: pointer; - padding: 0px 5px; - } -`; - -document.addEventListener("dragover", (dragOverEvent) => { - dragOverEvent.preventDefault(); - dragOverEvent.stopPropagation(); -}); - -export class FilesystemSelector extends LitElement { - static get styles() { - return componentCSS; - } - - static get properties() { - return { - value: { type: String, reflect: true }, - }; - } - - constructor(props = {}) { - super(); - if (props.onSelect) this.onSelect = props.onSelect; - if (props.onChange) this.onChange = props.onChange; - if (props.onThrow) this.onThrow = props.onThrow; - - this.accept = props.accept; - this.multiple = props.multiple; - this.type = props.type ?? "file"; - this.value = props.value ?? ""; - this.dialogOptions = props.dialogOptions ?? {}; - this.onChange = props.onChange ?? (() => {}); - this.dialogType = props.dialogType ?? "showOpenDialog"; - - this.addEventListener("change", () => this.onChange(this.value)); - } - - onSelect = () => {}; - onChange = () => {}; - #onThrow = (title, message) => { - message = message ? `
${title}
${message}` : title; - if (this.onThrow) this.onThrow(message); - throw new Error(message); - }; - - display = document.createElement("small"); - - #useElectronDialog = async (type) => { - const options = { ...this.dialogOptions }; - - if (!options.filters && this.accept) { - options.filters = [ - { - name: "Suggested Files", - extensions: this.accept.map((ext) => (ext[0] === "." ? ext.slice(1) : ext)), - }, - { name: "All Files", extensions: ["*"] }, - ]; - } - - options.properties = [ - type === "file" ? "openFile" : "openDirectory", - "noResolveAliases", - ...(options.properties ?? []), - ]; - - if (this.multiple && !options.properties.includes("multiSelections")) - options.properties.push("multiSelections"); - - this.classList.add("active"); - const result = await dialog[this.dialogType](options); - - this.classList.remove("active"); - if (result.canceled) return []; - return result; - }; - - #check = (value) => { - // Check type - const isLikelyFile = fs ? fs.statSync(value).isFile() : value.split(".").length; - if ((this.type === "directory" && isLikelyFile) || (this.type === "file" && !isLikelyFile)) - this.#onThrow("Incorrect filesystem object", `Please provide a ${this.type} instead.`); - }; - - #handleFiles = async (pathOrPaths, type) => { - const resolvedType = type ?? this.type; - - if (pathOrPaths) { - if (Array.isArray(pathOrPaths)) pathOrPaths.forEach(this.#check); - else if (!type) this.#check(pathOrPaths); - } - - let resolvedValue = pathOrPaths; - - if (Array.isArray(resolvedValue) && !this.multiple) { - if (resolvedValue.length > 1) - this.#onThrow( - `Too many ${resolvedType === "directory" ? "directories" : "files"} detected`, - `This selector will only accept one.` - ); - resolvedValue = resolvedValue[0]; - } - - if (this.multiple && !Array.isArray(resolvedValue)) resolvedValue = []; - - this.value = resolvedValue; - this.onSelect(this.value); - const event = new Event("change"); // Create a new change event - this.dispatchEvent(event); - }; - - async selectFormat(type = this.type) { - if (dialog) { - const results = await this.#useElectronDialog(type); - // const path = file.filePath ?? file.filePaths?.[0]; - const resolved = results.filePath ?? results.filePaths; - if (!resolved) return; // Cancelled - this.#handleFiles(results.filePath ?? results.filePaths, type); - } else { - let handles = await ( - type === "directory" - ? window.showDirectoryPicker() - : window.showOpenFilePicker({ multiple: this.multiple }) - ).catch(() => []); // Call using the same options - - const result = Array.isArray(handles) ? handles.map(({ name }) => name) : handles.name; - if (!result) return; // Cancelled - - this.#handleFiles(result, type); - } - } - - render() { - const isMultipleTypes = Array.isArray(this.type); - this.setAttribute("manytypes", isMultipleTypes); - const isArray = Array.isArray(this.value); - - const len = isArray ? this.value.length : 0; - - const resolvedValueDisplay = isArray - ? len > 1 - ? `${this.value[0]} and ${len - 1} other${len > 2 ? "s" : ""}` - : this.value[0] - : this.value; - - const objectTypeReference = getObjectTypeReferenceString(this.type, this.multiple); - - const instanceThis = this; - - return html` -
- - ${this.multiple && isArray && this.value.length > 1 - ? new List({ - items: this.value.map((v) => ({ value: v })), - editable: false, - onChange: function () { - instanceThis.value = this.items.map((item) => item.value); - }, - }) - : ""} - -
- ${this.value ? html`
this.#handleFiles()}>${unsafeSVG(restartSVG)}
` : ""} -
-
- ${isMultipleTypes - ? html`
- ${this.type.map( - (type) => - html` this.selectFormat(type)} - >Select - ${getObjectTypeReferenceString(type, this.multiple, { native: true })}` - )} -
` - : ""} - `; - } -} - -customElements.get("filesystem-selector") || customElements.define("filesystem-selector", FilesystemSelector); +import { LitElement, css, html } from "lit"; + +import { fs, remote } from "../electron/index"; +import { List } from "./List"; +const { dialog } = remote; + +import restartSVG from "../../assets/icons/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 + .map((type) => getObjectTypeReferenceString(type, multiple, { native, nested: true })) + .join(" / ")}`; + + const isDir = type === "directory"; + return multiple && (!isDir || (isDir && !native) || dialog) + ? type === "directory" + ? "directories" + : "files" + : nested + ? type + : `a ${type}`; +} + +const componentCSS = css` + * { + box-sizing: border-box; + } + + :host { + display: inline-block; + position: relative; + width: 100%; + } + + :host([manytypes="true"]) > button { + cursor: default; + } + + nwb-list { + width: 100px; + } + + #button-div { + margin-top: 10px; + display: flex; + gap: 5px; + } + + #button-div > nwb-button { + margin-bottom: 10px; + } + + button { + background: WhiteSmoke; + border: 1px solid #c3c3c3; + border-radius: 4px; + padding: 25px; + width: 100%; + color: dimgray; + cursor: pointer; + overflow-wrap: break-word; + text-align: center; + transition: background 0.5s; + } + + small { + color: silver; + } + + :host(.active) button { + background: rgb(240, 240, 240); + } + + .controls { + position: absolute; + right: 0; + bottom: 0; + cursor: pointer; + padding: 0px 5px; + } +`; + +document.addEventListener("dragover", (dragOverEvent) => { + dragOverEvent.preventDefault(); + dragOverEvent.stopPropagation(); +}); + +export class FilesystemSelector extends LitElement { + static get styles() { + return componentCSS; + } + + static get properties() { + return { + value: { type: String, reflect: true }, + }; + } + + constructor(props = {}) { + super(); + if (props.onSelect) this.onSelect = props.onSelect; + if (props.onChange) this.onChange = props.onChange; + if (props.onThrow) this.onThrow = props.onThrow; + + this.accept = props.accept; + this.multiple = props.multiple; + this.type = props.type ?? "file"; + this.value = props.value ?? ""; + this.dialogOptions = props.dialogOptions ?? {}; + this.onChange = props.onChange ?? (() => {}); + this.dialogType = props.dialogType ?? "showOpenDialog"; + + this.addEventListener("change", () => this.onChange(this.value)); + } + + onSelect = () => {}; + onChange = () => {}; + #onThrow = (title, message) => { + message = message ? `
${title}
${message}` : title; + if (this.onThrow) this.onThrow(message); + throw new Error(message); + }; + + display = document.createElement("small"); + + #useElectronDialog = async (type) => { + const options = { ...this.dialogOptions }; + + if (!options.filters && this.accept) { + options.filters = [ + { + name: "Suggested Files", + extensions: this.accept.map((ext) => (ext[0] === "." ? ext.slice(1) : ext)), + }, + { name: "All Files", extensions: ["*"] }, + ]; + } + + options.properties = [ + type === "file" ? "openFile" : "openDirectory", + "noResolveAliases", + ...(options.properties ?? []), + ]; + + if (this.multiple && !options.properties.includes("multiSelections")) + options.properties.push("multiSelections"); + + this.classList.add("active"); + const result = await dialog[this.dialogType](options); + + this.classList.remove("active"); + if (result.canceled) return []; + return result; + }; + + #check = (value) => { + // Check type + const isLikelyFile = fs ? fs.statSync(value).isFile() : value.split(".").length; + if ((this.type === "directory" && isLikelyFile) || (this.type === "file" && !isLikelyFile)) + this.#onThrow("Incorrect filesystem object", `Please provide a ${this.type} instead.`); + }; + + #handleFiles = async (pathOrPaths, type) => { + const resolvedType = type ?? this.type; + + if (pathOrPaths) { + if (Array.isArray(pathOrPaths)) pathOrPaths.forEach(this.#check); + else if (!type) this.#check(pathOrPaths); + } + + let resolvedValue = pathOrPaths; + + if (Array.isArray(resolvedValue) && !this.multiple) { + if (resolvedValue.length > 1) + this.#onThrow( + `Too many ${resolvedType === "directory" ? "directories" : "files"} detected`, + `This selector will only accept one.` + ); + resolvedValue = resolvedValue[0]; + } + + if (this.multiple && !Array.isArray(resolvedValue)) resolvedValue = []; + + this.value = resolvedValue; + this.onSelect(this.value); + const event = new Event("change"); // Create a new change event + this.dispatchEvent(event); + }; + + async selectFormat(type = this.type) { + if (dialog) { + const results = await this.#useElectronDialog(type); + // const path = file.filePath ?? file.filePaths?.[0]; + const resolved = results.filePath ?? results.filePaths; + if (!resolved) return; // Cancelled + this.#handleFiles(results.filePath ?? results.filePaths, type); + } else { + let handles = await ( + type === "directory" + ? window.showDirectoryPicker() + : window.showOpenFilePicker({ multiple: this.multiple }) + ).catch(() => []); // Call using the same options + + const result = Array.isArray(handles) ? handles.map(({ name }) => name) : handles.name; + if (!result) return; // Cancelled + + this.#handleFiles(result, type); + } + } + + render() { + const isMultipleTypes = Array.isArray(this.type); + this.setAttribute("manytypes", isMultipleTypes); + const isArray = Array.isArray(this.value); + + const len = isArray ? this.value.length : 0; + + const resolvedValueDisplay = isArray + ? len > 1 + ? `${this.value[0]} and ${len - 1} other${len > 2 ? "s" : ""}` + : this.value[0] + : this.value; + + const objectTypeReference = getObjectTypeReferenceString(this.type, this.multiple); + + const instanceThis = this; + + return html` +
+ + ${this.multiple && isArray && this.value.length > 1 + ? new List({ + items: this.value.map((v) => ({ value: v })), + editable: false, + onChange: function () { + instanceThis.value = this.items.map((item) => item.value); + }, + }) + : ""} + +
+ ${this.value ? html`
this.#handleFiles()}>${unsafeSVG(restartSVG)}
` : ""} +
+
+ ${isMultipleTypes + ? html`
+ ${this.type.map( + (type) => + html` this.selectFormat(type)} + >Select + ${getObjectTypeReferenceString(type, this.multiple, { native: true })}` + )} +
` + : ""} + `; + } +} + +customElements.get("filesystem-selector") || customElements.define("filesystem-selector", FilesystemSelector); diff --git a/src/electron/renderer/src/stories/Search.js b/src/electron/renderer/src/stories/Search.js index 023a775f8..5b133e2f0 100644 --- a/src/electron/renderer/src/stories/Search.js +++ b/src/electron/renderer/src/stories/Search.js @@ -1,524 +1,524 @@ -import { LitElement, html, css } from "lit"; -import { styleMap } from "lit/directives/style-map.js"; - -import searchSVG from "../../assets/icons/search.svg?raw"; - -import tippy from "tippy.js"; -import { unsafeHTML } from "lit/directives/unsafe-html.js"; - -const ALTERNATIVE_MODES = ["input", "append"]; - -export class Search extends LitElement { - constructor({ - value, - options, - showAllWhenEmpty = true, - listMode = "list", - headerStyles = {}, - disabledLabel, - onSelect, - strict = false, - } = {}) { - super(); - this.#value = value; - this.options = options; - this.showAllWhenEmpty = showAllWhenEmpty; - this.disabledLabel = disabledLabel; - this.listMode = listMode; - this.headerStyles = headerStyles; - this.strict = strict; - if (onSelect) this.onSelect = onSelect; - - // document.addEventListener("click", () => this.#close()); - } - - #close = () => { - if (this.listMode === "input" && this.getAttribute("interacted") === "true") { - this.setAttribute("interacted", false); - this.#onSelect(this.getSelectedOption()); - } else if (this.listMode !== "list") { - this.setAttribute("active", false); - } - }; - - #value; - - #isObject(value = this.#value) { - return value && typeof value === "object"; - } - - #getOption = ({ label, value } = {}) => { - return this.options.find((item) => { - if (label && item.label === label) return true; - if (value && value === item.value) return true; - }); - }; - - getSelectedOption = () => { - const inputValue = (this.shadowRoot.querySelector("input") ?? this).value; - const matched = this.#getOption({ label: inputValue }); - return matched ?? { value: inputValue }; - }; - - get value() { - return this.#isObject() ? this.#value.value : this.#value; - } - - set value(val) { - this.#value = val; - this.requestUpdate(); - } - - static get styles() { - return css` - * { - box-sizing: border-box; - } - - :host { - position: relative; - display: flex; - flex-direction: column; - background: white; - border-radius: 5px; - width: 100%; - height: 100%; - } - - .header { - background: white; - position: relative; - } - - input { - width: 100%; - border-radius: 4px; - padding: 10px 12px; - font-size: 100%; - font-weight: normal; - border: 1px solid var(--color-border); - transition: border-color 150ms ease-in-out 0s; - outline: none; - color: rgb(33, 49, 60); - background-color: rgb(255, 255, 255); - } - - input::placeholder { - opacity: 0.5; - } - - ul { - list-style: none; - padding: 0; - margin: 0; - position: relative; - background: white; - border: 1px solid var(--color-border); - overflow: auto; - } - - :host([listmode="input"]) ul, - :host([listmode="append"]) ul { - position: absolute; - top: 38px; - 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; - } - - a { - text-decoration: none; - } - - a:after { - content: "🔗"; - padding-left: 2px; - font-size: 60%; - } - - :host([listmode="input"]) svg, - :host([listmode="append"]) 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 { - visibility: hidden; - } - - .option { - padding: 10px 18px; - border-top: 1px solid #f2f2f2; - } - - .category { - padding: 10px 18px; - background: gainsboro; - border-top: 1px solid gray; - border-bottom: 1px solid gray; - font-size: 90%; - font-weight: bold; - position: sticky; - top: 0; - z-index: 1; - } - - .structured-keywords { - font-size: 90%; - color: dimgrey; - } - - .option:hover { - background: #f2f2f2; - cursor: pointer; - } - - .label { - margin: 0; - } - - .label { - display: flex; - gap: 10px; - } - - [disabled]:not([hidden]) { - display: flex; - justify-content: space-between; - align-items: center; - pointer-events: none; - opacity: 0.4; - } - - [disabled]::after { - content: var(--disabled-label, "Not available"); - text-align: left; - padding-left: 25px; - opacity: 70%; - font-size: 90%; - white-space: nowrap; - min-width: min-content; - } - `; - } - - static get properties() { - return { - options: { type: Object }, - showAllWhenEmpty: { type: Boolean }, - listMode: { type: String, reflect: true }, - strict: { type: Boolean, reflect: true }, - }; - } - - updated() { - const options = this.shadowRoot.querySelectorAll(".option"); - this.#options = Array.from(options).map((option) => { - const keywordString = option.getAttribute("data-keywords"); - const structuredKeywordString = option.getAttribute("data-structured-keywords"); - - const keywords = keywordString ? JSON.parse(option.getAttribute("data-keywords")) : []; - const structuredKeywords = structuredKeywordString ? JSON.parse(structuredKeywordString) : {}; - - return { option, keywords, structuredKeywords, label: option.querySelector(".label").innerText }; - }); - - this.#initialize(); - - if (!ALTERNATIVE_MODES.includes(this.listMode)) this.#populate(); - } - - onSelect = (id, value) => {}; - - #displayValue = (option) => { - return option?.label ?? option?.value ?? option?.key ?? (this.#isObject(option) ? undefined : option); - }; - - #onSelect = (option) => { - const inputMode = this.listMode === "input"; - const input = this.shadowRoot.querySelector("input"); - - const selectedOption = this.#getOption({ value: option.value }); - - if (inputMode) this.setAttribute("active", false); - - if (this.strict && !selectedOption) { - input.value = this.#value.label; - return; - } - - if (inputMode) { - this.value = selectedOption ?? option; - input.value = this.#displayValue(option); - return this.onSelect(option); - } - - input.value = ""; - this.#initialize(); - this.onSelect(option); - }; - - #options = []; - #initialize = () => { - this.#options.forEach(({ option }) => - option[this.showAllWhenEmpty ? "removeAttribute" : "setAttribute"]("hidden", "") - ); - - if (!this.showAllWhenEmpty) this.setAttribute("active", false); - }; - - list = document.createElement("ul"); - - categories = {}; - - #sortedCategories = []; - - getTokens = (input) => - input - .replaceAll(/[^\w\s]/g, "") - .split(" ") - .map((token) => token.trim().toLowerCase()) - .filter((token) => token); - - #populate = (input = this.value ?? "") => { - const toShow = []; - - const inputTokens = this.getTokens(input); - - // Check if the input value matches the label or any of the keywords - this.#options.forEach(({ label, keywords, structuredKeywords }, i) => { - const labelTokens = this.getTokens(label); - const allKeywords = [...keywords, ...Object.values(structuredKeywords).flat()]; - const allKeywordTokens = allKeywords.map((keyword) => this.getTokens(keyword)).flat(); - const allTokens = [...labelTokens, ...allKeywordTokens]; - - const result = inputTokens.reduce((acc, token) => { - for (let subtoken of allTokens) { - if (subtoken.startsWith(token) && !toShow.includes(i)) return (acc += 1); - } - return acc; - }, 0); - - if (result === inputTokens.length) toShow.push(i); - }); - - this.#options.forEach(({ option }, i) => { - if (toShow.includes(i) || !inputTokens.length) { - option.removeAttribute("hidden"); - } else { - option.setAttribute("hidden", ""); - } - }); - - this.#sortedCategories.forEach(({ entries, element }) => { - if (entries.reduce((acc, entryElement) => acc + entryElement.hasAttribute("hidden"), 0) === entries.length) - element.setAttribute("hidden", ""); - else element.removeAttribute("hidden"); - }); - - this.setAttribute("active", !!toShow.length || !inputTokens.length); - this.setAttribute("interacted", true); - }; - - #ignore = false; - - render() { - this.categories = {}; - - // Update list - this.list.remove(); - this.list = document.createElement("ul"); - - if (this.disabledLabel) this.style.setProperty("--disabled-label", `"${this.disabledLabel}"`); - else this.style.removeProperty("--disabled-label"); - - const slot = document.createElement("slot"); - this.list.appendChild(slot); - - if (this.options) { - const options = this.options.map((item) => { - return { - label: item.key, - ...item, - }; - }); - - const itemEls = options - .sort((a, b) => { - if (a.label < b.label) return -1; - if (a.label > b.label) return 1; - return 0; - }) // Sort alphabetically - .sort((a, b) => { - if (a.disabled && b.disabled) return 0; - else if (a.disabled) return 1; - else if (b.disabled) return -1; - }) // Sort with the disabled options at the bottom - .map((option) => { - const listItemElement = document.createElement("li"); - listItemElement.classList.add("option"); - listItemElement.setAttribute("hidden", ""); - listItemElement.setAttribute("tabindex", -1); - - const { disabled, structuredKeywords, keywords } = option; - - if (structuredKeywords) - listItemElement.setAttribute( - "data-structured-keywords", - JSON.stringify(option.structuredKeywords) - ); - if (keywords) listItemElement.setAttribute("data-keywords", JSON.stringify(option.keywords)); - - listItemElement.addEventListener("click", (clickEvent) => { - clickEvent.stopPropagation(); - if (this.#ignore) return (this.#ignore = false); - this.#onSelect(option); - }); - - if (disabled) listItemElement.setAttribute("disabled", ""); - - const container = document.createElement("div"); - - const label = document.createElement("h4"); - label.classList.add("label"); - label.innerText = option.label; - - if (option.description || option.link) { - const info = option.link ? document.createElement("a") : document.createElement("span"); - if (option.link) { - info.setAttribute("data-link", true); - info.href = option.link; - info.target = "_blank"; - } - - info.innerText = "ℹ️"; - label.append(info); - - if (option.description) - tippy(info, { - content: `

${option.description}

`, - allowHTML: true, - placement: "right", - }); - } - - container.appendChild(label); - - if (keywords) { - const keywordsElement = document.createElement("small"); - keywordsElement.classList.add("keywords"); - keywordsElement.innerText = option.keywords.join(", "); - container.appendChild(keywordsElement); - } - - if (structuredKeywords) { - const div = document.createElement("div"); - div.classList.add("structured-keywords"); - - Object.entries(structuredKeywords).forEach(([key, value]) => { - const keywordsElement = document.createElement("small"); - const capitalizedKey = key[0].toUpperCase() + key.slice(1); - keywordsElement.innerHTML = `${capitalizedKey}: ${value.join(", ")}`; - div.appendChild(keywordsElement); - }); - - container.appendChild(div); - } - - listItemElement.append(container); - - if (option.category) { - let category = this.categories[option.category]; - if (!category) { - category = document.createElement("div"); - category.innerText = option.category; - category.classList.add("category"); - this.categories[option.category] = { - entries: [], - element: category, - }; - } - - this.categories[option.category].entries.push(listItemElement); - return; - } - - return listItemElement; - }) - .filter((listItemElement) => listItemElement); - - this.list.append(...itemEls); - } - - // Categories sorted alphabetically - 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); - }); - - const valueToDisplay = this.#displayValue(this.#value); - - return html` -
- { - clickEvent.stopPropagation(); - if (ALTERNATIVE_MODES.includes(this.listMode)) { - const input = clickEvent.target.value; - this.#populate(input); - } - }} - - @input=${(inputEvent) => { - const input = inputEvent.target.value; - this.#populate(input); - }} - - @blur=${(blurEvent) => { - const relatedTarget = blurEvent.relatedTarget; - if (relatedTarget) { - if (relatedTarget.classList.contains("option")) return; - if (relatedTarget.hasAttribute("data-link")) { - this.#ignore = true; - return; - } - } - - this.#close(); - }} - - > - ${unsafeHTML(searchSVG)} -
- ${this.list} - `; - } -} - -customElements.get("nwb-search") || customElements.define("nwb-search", Search); +import { LitElement, html, css } from "lit"; +import { styleMap } from "lit/directives/style-map.js"; + +import searchSVG from "../../assets/icons/search.svg?raw"; + +import tippy from "tippy.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; + +const ALTERNATIVE_MODES = ["input", "append"]; + +export class Search extends LitElement { + constructor({ + value, + options, + showAllWhenEmpty = true, + listMode = "list", + headerStyles = {}, + disabledLabel, + onSelect, + strict = false, + } = {}) { + super(); + this.#value = value; + this.options = options; + this.showAllWhenEmpty = showAllWhenEmpty; + this.disabledLabel = disabledLabel; + this.listMode = listMode; + this.headerStyles = headerStyles; + this.strict = strict; + if (onSelect) this.onSelect = onSelect; + + // document.addEventListener("click", () => this.#close()); + } + + #close = () => { + if (this.listMode === "input" && this.getAttribute("interacted") === "true") { + this.setAttribute("interacted", false); + this.#onSelect(this.getSelectedOption()); + } else if (this.listMode !== "list") { + this.setAttribute("active", false); + } + }; + + #value; + + #isObject(value = this.#value) { + return value && typeof value === "object"; + } + + #getOption = ({ label, value } = {}) => { + return this.options.find((item) => { + if (label && item.label === label) return true; + if (value && value === item.value) return true; + }); + }; + + getSelectedOption = () => { + const inputValue = (this.shadowRoot.querySelector("input") ?? this).value; + const matched = this.#getOption({ label: inputValue }); + return matched ?? { value: inputValue }; + }; + + get value() { + return this.#isObject() ? this.#value.value : this.#value; + } + + set value(val) { + this.#value = val; + this.requestUpdate(); + } + + static get styles() { + return css` + * { + box-sizing: border-box; + } + + :host { + position: relative; + display: flex; + flex-direction: column; + background: white; + border-radius: 5px; + width: 100%; + height: 100%; + } + + .header { + background: white; + position: relative; + } + + input { + width: 100%; + border-radius: 4px; + padding: 10px 12px; + font-size: 100%; + font-weight: normal; + border: 1px solid var(--color-border); + transition: border-color 150ms ease-in-out 0s; + outline: none; + color: rgb(33, 49, 60); + background-color: rgb(255, 255, 255); + } + + input::placeholder { + opacity: 0.5; + } + + ul { + list-style: none; + padding: 0; + margin: 0; + position: relative; + background: white; + border: 1px solid var(--color-border); + overflow: auto; + } + + :host([listmode="input"]) ul, + :host([listmode="append"]) ul { + position: absolute; + top: 38px; + 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; + } + + a { + text-decoration: none; + } + + a:after { + content: "🔗"; + padding-left: 2px; + font-size: 60%; + } + + :host([listmode="input"]) svg, + :host([listmode="append"]) 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 { + visibility: hidden; + } + + .option { + padding: 10px 18px; + border-top: 1px solid #f2f2f2; + } + + .category { + padding: 10px 18px; + background: gainsboro; + border-top: 1px solid gray; + border-bottom: 1px solid gray; + font-size: 90%; + font-weight: bold; + position: sticky; + top: 0; + z-index: 1; + } + + .structured-keywords { + font-size: 90%; + color: dimgrey; + } + + .option:hover { + background: #f2f2f2; + cursor: pointer; + } + + .label { + margin: 0; + } + + .label { + display: flex; + gap: 10px; + } + + [disabled]:not([hidden]) { + display: flex; + justify-content: space-between; + align-items: center; + pointer-events: none; + opacity: 0.4; + } + + [disabled]::after { + content: var(--disabled-label, "Not available"); + text-align: left; + padding-left: 25px; + opacity: 70%; + font-size: 90%; + white-space: nowrap; + min-width: min-content; + } + `; + } + + static get properties() { + return { + options: { type: Object }, + showAllWhenEmpty: { type: Boolean }, + listMode: { type: String, reflect: true }, + strict: { type: Boolean, reflect: true }, + }; + } + + updated() { + const options = this.shadowRoot.querySelectorAll(".option"); + this.#options = Array.from(options).map((option) => { + const keywordString = option.getAttribute("data-keywords"); + const structuredKeywordString = option.getAttribute("data-structured-keywords"); + + const keywords = keywordString ? JSON.parse(option.getAttribute("data-keywords")) : []; + const structuredKeywords = structuredKeywordString ? JSON.parse(structuredKeywordString) : {}; + + return { option, keywords, structuredKeywords, label: option.querySelector(".label").innerText }; + }); + + this.#initialize(); + + if (!ALTERNATIVE_MODES.includes(this.listMode)) this.#populate(); + } + + onSelect = (id, value) => {}; + + #displayValue = (option) => { + return option?.label ?? option?.value ?? option?.key ?? (this.#isObject(option) ? undefined : option); + }; + + #onSelect = (option) => { + const inputMode = this.listMode === "input"; + const input = this.shadowRoot.querySelector("input"); + + const selectedOption = this.#getOption({ value: option.value }); + + if (inputMode) this.setAttribute("active", false); + + if (this.strict && !selectedOption) { + input.value = this.#value.label; + return; + } + + if (inputMode) { + this.value = selectedOption ?? option; + input.value = this.#displayValue(option); + return this.onSelect(option); + } + + input.value = ""; + this.#initialize(); + this.onSelect(option); + }; + + #options = []; + #initialize = () => { + this.#options.forEach(({ option }) => + option[this.showAllWhenEmpty ? "removeAttribute" : "setAttribute"]("hidden", "") + ); + + if (!this.showAllWhenEmpty) this.setAttribute("active", false); + }; + + list = document.createElement("ul"); + + categories = {}; + + #sortedCategories = []; + + getTokens = (input) => + input + .replaceAll(/[^\w\s]/g, "") + .split(" ") + .map((token) => token.trim().toLowerCase()) + .filter((token) => token); + + #populate = (input = this.value ?? "") => { + const toShow = []; + + const inputTokens = this.getTokens(input); + + // Check if the input value matches the label or any of the keywords + this.#options.forEach(({ label, keywords, structuredKeywords }, i) => { + const labelTokens = this.getTokens(label); + const allKeywords = [...keywords, ...Object.values(structuredKeywords).flat()]; + const allKeywordTokens = allKeywords.map((keyword) => this.getTokens(keyword)).flat(); + const allTokens = [...labelTokens, ...allKeywordTokens]; + + const result = inputTokens.reduce((acc, token) => { + for (let subtoken of allTokens) { + if (subtoken.startsWith(token) && !toShow.includes(i)) return (acc += 1); + } + return acc; + }, 0); + + if (result === inputTokens.length) toShow.push(i); + }); + + this.#options.forEach(({ option }, i) => { + if (toShow.includes(i) || !inputTokens.length) { + option.removeAttribute("hidden"); + } else { + option.setAttribute("hidden", ""); + } + }); + + this.#sortedCategories.forEach(({ entries, element }) => { + if (entries.reduce((acc, entryElement) => acc + entryElement.hasAttribute("hidden"), 0) === entries.length) + element.setAttribute("hidden", ""); + else element.removeAttribute("hidden"); + }); + + this.setAttribute("active", !!toShow.length || !inputTokens.length); + this.setAttribute("interacted", true); + }; + + #ignore = false; + + render() { + this.categories = {}; + + // Update list + this.list.remove(); + this.list = document.createElement("ul"); + + if (this.disabledLabel) this.style.setProperty("--disabled-label", `"${this.disabledLabel}"`); + else this.style.removeProperty("--disabled-label"); + + const slot = document.createElement("slot"); + this.list.appendChild(slot); + + if (this.options) { + const options = this.options.map((item) => { + return { + label: item.key, + ...item, + }; + }); + + const itemEls = options + .sort((a, b) => { + if (a.label < b.label) return -1; + if (a.label > b.label) return 1; + return 0; + }) // Sort alphabetically + .sort((a, b) => { + if (a.disabled && b.disabled) return 0; + else if (a.disabled) return 1; + else if (b.disabled) return -1; + }) // Sort with the disabled options at the bottom + .map((option) => { + const listItemElement = document.createElement("li"); + listItemElement.classList.add("option"); + listItemElement.setAttribute("hidden", ""); + listItemElement.setAttribute("tabindex", -1); + + const { disabled, structuredKeywords, keywords } = option; + + if (structuredKeywords) + listItemElement.setAttribute( + "data-structured-keywords", + JSON.stringify(option.structuredKeywords) + ); + if (keywords) listItemElement.setAttribute("data-keywords", JSON.stringify(option.keywords)); + + listItemElement.addEventListener("click", (clickEvent) => { + clickEvent.stopPropagation(); + if (this.#ignore) return (this.#ignore = false); + this.#onSelect(option); + }); + + if (disabled) listItemElement.setAttribute("disabled", ""); + + const container = document.createElement("div"); + + const label = document.createElement("h4"); + label.classList.add("label"); + label.innerText = option.label; + + if (option.description || option.link) { + const info = option.link ? document.createElement("a") : document.createElement("span"); + if (option.link) { + info.setAttribute("data-link", true); + info.href = option.link; + info.target = "_blank"; + } + + info.innerText = "ℹ️"; + label.append(info); + + if (option.description) + tippy(info, { + content: `

${option.description}

`, + allowHTML: true, + placement: "right", + }); + } + + container.appendChild(label); + + if (keywords) { + const keywordsElement = document.createElement("small"); + keywordsElement.classList.add("keywords"); + keywordsElement.innerText = option.keywords.join(", "); + container.appendChild(keywordsElement); + } + + if (structuredKeywords) { + const div = document.createElement("div"); + div.classList.add("structured-keywords"); + + Object.entries(structuredKeywords).forEach(([key, value]) => { + const keywordsElement = document.createElement("small"); + const capitalizedKey = key[0].toUpperCase() + key.slice(1); + keywordsElement.innerHTML = `${capitalizedKey}: ${value.join(", ")}`; + div.appendChild(keywordsElement); + }); + + container.appendChild(div); + } + + listItemElement.append(container); + + if (option.category) { + let category = this.categories[option.category]; + if (!category) { + category = document.createElement("div"); + category.innerText = option.category; + category.classList.add("category"); + this.categories[option.category] = { + entries: [], + element: category, + }; + } + + this.categories[option.category].entries.push(listItemElement); + return; + } + + return listItemElement; + }) + .filter((listItemElement) => listItemElement); + + this.list.append(...itemEls); + } + + // Categories sorted alphabetically + 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); + }); + + const valueToDisplay = this.#displayValue(this.#value); + + return html` +
+ { + clickEvent.stopPropagation(); + if (ALTERNATIVE_MODES.includes(this.listMode)) { + const input = clickEvent.target.value; + this.#populate(input); + } + }} + + @input=${(inputEvent) => { + const input = inputEvent.target.value; + this.#populate(input); + }} + + @blur=${(blurEvent) => { + const relatedTarget = blurEvent.relatedTarget; + if (relatedTarget) { + if (relatedTarget.classList.contains("option")) return; + if (relatedTarget.hasAttribute("data-link")) { + this.#ignore = true; + return; + } + } + + this.#close(); + }} + + > + ${unsafeHTML(searchSVG)} +
+ ${this.list} + `; + } +} + +customElements.get("nwb-search") || customElements.define("nwb-search", Search); diff --git a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js index 18e463156..0efe5162a 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js @@ -1,600 +1,600 @@ -import { JSONSchemaForm, getSchema } from "../../../JSONSchemaForm.js"; - -import { InstanceManager } from "../../../InstanceManager.js"; -import { ManagedPage } from "./ManagedPage.js"; -import { Modal } from "../../../Modal"; - -import { validateOnChange } from "../../../../validation/index.js"; -import { - resolveGlobalOverrides, - resolveMetadata, - getInfoFromId, - drillSchemaProperties, - resolveFromPath, -} from "./utils"; - -import Swal from "sweetalert2"; -import { SimpleTable } from "../../../SimpleTable.js"; -import { onThrow } from "../../../../errors"; -import { merge } from "../../utils"; -import { NWBFilePreview } from "../../../preview/NWBFilePreview.js"; -import { header, tempPropertyKey } from "../../../forms/utils"; - -import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; -import { Button } from "../../../Button.js"; - -import globalIcon from "../../../../../assets/icons/global.svg?raw"; - -const parentTableRenderConfig = { - Electrodes: (metadata) => { - metadata.schema.description = "Download, modify, and re-upload data to change the electrode information."; - return true; - }, - Units: (metadata) => { - metadata.truncated = true; - metadata.schema.description = "Update unit information directly on your source data."; - return true; - }, -}; - -function getAggregateRequirements(path) { - const electrodeSchema = getSchema(path, this.schema); - return Object.values(electrodeSchema.properties).reduce((set, schema) => { - schema.items.required.forEach((item) => set.add(item)); - return set; - }, new Set()); -} - -const tableRenderConfig = { - "*": (metadata) => new SimpleTable(metadata), - ElectrodeColumns: function (metadata) { - const aggregateRequirements = getAggregateRequirements.call(this, ["Ecephys", "Electrodes"]); - - return new SimpleTable({ - ...metadata, - editable: { - name: (value) => !aggregateRequirements.has(value), - data_type: (_, row) => !aggregateRequirements.has(row.name), - __row_remove: (_, row) => !aggregateRequirements.has(row.name), - }, - }); - }, - UnitColumns: function (metadata) { - metadata.editable = false; - metadata.schema.description = "Update unit information directly on your source data."; - - return true; - - // const aggregateRequirements = getAggregateRequirements.call(this, ["Ecephys", "Units"]); - // - // return new SimpleTable({ - // ...metadata, - // contextOptions: { - // row: { - // add: false, - // remove: false, - // }, - // }, - // editable: { - // name: (value) => !aggregateRequirements.has(value), - // data_type: (_, row) => !aggregateRequirements.has(row.name), - // }, - // }); - }, -}; - -const imagingPlaneKey = "imaging_plane"; -const propsToIgnore = { - NWBFile: { - session_id: true, - source_script: true, - source_script_file_name: true, - identifier: true, - }, - Subject: { - subject_id: true, - }, - Ophys: { - "*": { - starting_time: true, - rate: true, - conversion: true, - offset: true, - unit: true, - control: true, - comments: true, - control_description: true, - }, - ImagingPlane: { - [imagingPlaneKey]: true, - manifold: true, - }, - TwoPhotonSeries: { - [imagingPlaneKey]: true, - format: true, - starting_frame: true, - control: true, - control_description: true, - comments: true, - resolution: true, - dimension: true, - device: true, - }, - }, - Ecephys: { - ElectricalSeries: true, - ElectricalSeriesLF: true, - ElectricalSeriesAP: true, - ElectricalSeriesNIDQ: true, - Units: { - "*": { - UnitColumns: { - "*": { - data_type: true, // Do not show data_type - }, - }, - }, - }, - }, - Icephys: true, // Always ignore icephys metadata (for now) - Behavior: true, // Always ignore behavior metadata (for now) - "ndx-dandi-icephys": true, -}; - -import { preprocessMetadataSchema } from "../../../../../../../schemas/base-metadata.schema"; -import { - createTable, - getEditableItems, - isPatternProperties, - isAdditionalProperties, -} from "../../../JSONSchemaInput.js"; -import { html } from "lit"; - -export class GuidedMetadataPage extends ManagedPage { - constructor(...args) { - super(...args); - this.style.height = "100%"; // Fix main section - } - - beforeSave = () => { - merge(this.localState.results, this.info.globalState.results); - }; - - form; - - #globalButton = new Button({ - icon: globalIcon, - label: "Edit Default Metadata", - onClick: () => { - this.#globalModal.form.results = structuredClone(this.info.globalState.project); - this.#globalModal.open = true; - }, - }); - - workflow = { - multiple_sessions: { - elements: [this.#globalButton], - }, - }; - - header = { - controls: [this.#globalButton], - subtitle: "Edit all metadata for this conversion at the session level", - }; - - footer = { - onNext: async () => { - await this.save(); // Save in case the conversion fails - for (let { form } of this.forms) await form.validate(); // Will throw an error in the callback - return this.to(1); // Will trigger preview conversion if necessary - }, - }; - - #globalModal = null; - - connectedCallback() { - super.connectedCallback(); - - // 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: "Default Metadata", - propsToRemove: toRemove, - schema, - hasInstances: true, - mergeFunction: function (globalResolved, globals) { - merge(globalResolved, globals); - return resolveGlobalOverrides(this.subject, globals); - }, - formProps: { - validateOnChange, - }, - })); - document.body.append(modal); - } - - disconnectedCallback() { - super.disconnectedCallback(); - if (this.#globalModal) this.#globalModal.remove(); - } - - createForm = ({ subject, session, info }) => { - const hasMultipleSessions = this.workflow.multiple_sessions.value; - - // const results = createResults({ subject, info }, this.info.globalState); - - const { globalState } = this.info; - - const results = info.metadata; // Edited form info - - // Define the appropriate global metadata to fill empty values in the form - const aggregateGlobalMetadata = resolveGlobalOverrides(subject, globalState, hasMultipleSessions); - - // Define the correct instance identifier - const instanceId = `sub-${subject}/ses-${session}`; - - // Ignore specific metadata in the form by removing their schema value - const schema = preprocessMetadataSchema(globalState.schema.metadata[subject][session]); - delete schema.description; - - resolveMetadata(subject, session, globalState); - - const additionalPropertiesToRetitle = ["Ophys.ImageSegmentation"]; - - const patternPropsToRetitle = ["Ophys.Fluorescence", "Ophys.DfOverF", "Ophys.SegmentationImages"]; - - console.log("schema", structuredClone(schema), structuredClone(results)); - - const ophys = schema.properties.Ophys; - if (ophys) { - drillSchemaProperties( - schema, - (path, schema, target, isPatternProperties, parentSchema) => { - if (path[0] === "Ophys") { - const name = path.slice(-1)[0]; - - if (isPatternProperties) schema.minItems = schema.maxItems = null; // Do not allow more than on the results - - if (schema.type === "array") { - if (name !== "Device" && target) { - // Set most Ophys tables to have minItems / maxItems equal (i.e. no editing possible) - if (name in target) schema.minItems = schema.maxItems = null; - // Remove Ophys property requirement if left initially undefined - else { - target[name] = []; // Initialize empty array - if (parentSchema.required.includes(name)) - parentSchema.required = parentSchema.required.filter((n) => n !== name); - } - } - } - } - }, - results - ); - } - - console.log("schema", structuredClone(schema), structuredClone(results)); - - delete results.__generated; // Ignore generated results. NOTE: See if this fails - - // Create the form - const form = new JSONSchemaForm({ - identifier: instanceId, - schema, - results, - globals: aggregateGlobalMetadata, - - ignore: propsToIgnore, - onOverride: (name) => { - this.notify(`${header(name)} has been overridden with a global value.`, "warning", 3000); - }, - - transformErrors: (error) => { - // JSON Schema Exceptions - if (error.message.includes('does not conform to the "date-time" format.')) return false; - if (error.message.includes('not allowed to have the additional property "Ecephys".')) return false; // NOTE: Remove after including Ecephys metadata - }, - - groups: [ - { - name: "Subject Age", - properties: [ - ["Subject", "age"], - ["Subject", "date_of_birth"], - ], - link: true, - conditional: true, - }, - { - name: "Institutional Info", - properties: [ - ["NWBFile", "institution"], - ["NWBFile", "lab"], - ], - }, - - // For C. Elegans - { - properties: [ - ["Subject", "sex"], - ["Subject", "species"], - ], - link: true, - }, - ], - - // deferLoading: true, - onLoaded: () => { - this.#nLoaded++; - this.#checkAllLoaded(); - }, - - onUpdate: () => (this.unsavedUpdates = "conversions"), - - validateOnChange, - onlyRequired: false, - onStatusChange: (state) => this.manager.updateState(`sub-${subject}/ses-${session}`, state), - - renderCustomHTML: function (name, inputSchema, localPath, { onUpdate, onThrow }) { - if (name === "TwoPhotonSeries" && (!this.value || !this.value.length)) return null; - if (name === "Device" && (!this.value || !this.value.length)) return null; - if (name === "ElectrodeGroup" && (!this.value || !this.value.length)) return null; - - const isAdditional = isAdditionalProperties(this.pattern); - const isPattern = isPatternProperties(this.pattern); - - if (isAdditional || isPattern) { - // One table with nested tables for each property - const data = getEditableItems(this.value, this.pattern, { name, schema: this.schema }).reduce( - (acc, { key, value }) => { - acc[key] = value; - return acc; - }, - {} - ); - - const nProps = Object.keys(data).length; - - const schemaCopy = { ...inputSchema }; - - if (additionalPropertiesToRetitle.includes(this.form.base.join("."))) { - inputSchema.title = ""; - - return Object.entries(data).map(([name, value]) => { - const mockInput = { - schema: { - type: "array", - items: { - type: "object", - additionalProperties: true, - }, - }, - renderTable: this.renderTable, - value, - form: { - ignore: this.form.ignore, - }, - }; - - const table = createTable.call(mockInput, [...localPath], { - onUpdate: (localPath, value) => - onUpdate([name, ...localPath], value, true, { - willTimeout: false, - onError: (e) => e, - onWarning: (e) => e, - }), - onThrow: onThrow, - }); - - return html` -
-

${header(name)}

- ${table} -
- `; - }); - } - - if (patternPropsToRetitle.includes(this.form.base.join("."))) { - inputSchema.title = "Plane Metadata
"; - - return html`
- ${Object.entries(data) - .map(([name, value]) => { - const createNestedTable = (value, pattern, schema) => { - const mockInput = { - schema: { - type: "object", - items: schema, - - // Transfer a subset of item schema values - minItems: schema.minItems, - maxItems: schema.maxItems, - }, - - renderTable: this.renderTable, - value, - pattern: pattern, - form: { - ignore: this.form.ignore, - }, - }; - - return html` -
- ${nProps > 1 ? html`

${header(name)}

` : ""} - ${createTable.call(mockInput, [...localPath], { - overrides: { - schema: { - items: { - order: ["name", "description"], - additionalProperties: false, - }, - }, - ignore: { - [tempPropertyKey]: true, - }, - }, - onUpdate: (localPath, value) => - onUpdate([name, ...localPath], value, true, { - willTimeout: false, - onError: (e) => e, - onWarning: (e) => e, - }), - onThrow: onThrow, - })} -
- `; - }; - - if (isAdditional) { - const data = value.reduce((acc, item) => { - const name = item.name; - acc[name] = item; - return acc; - }, {}); - - return createNestedTable(data, undefined, { - type: "object", - items: { - type: "object", - additionalProperties: true, - }, - }); - } - - return Object.entries(schemaCopy.patternProperties).map(([pattern, schema]) => { - return createNestedTable(value, pattern, schema); - }); - }) - .flat()} -
`; - } - } - }, - - renderTable: function (name, metadata, fullPath) { - const updatedSchema = structuredClone(metadata.schema); - metadata.schema = updatedSchema; - - const parentName = fullPath[fullPath.length - 1]; - - const tableConfig = - tableRenderConfig[name] ?? parentTableRenderConfig[parentName] ?? tableRenderConfig["*"] ?? true; - if (typeof tableConfig === "function") - return tableConfig.call(form, metadata, [...fullPath, name], this); - else return tableConfig; - }, - onThrow, - }); - - return { - subject, - session, - form, - }; - }; - - #nLoaded = 0; - #loaded = false; - - #checkAllLoaded = () => { - if (this.#nLoaded === this.forms.length) this.#onLoaded(); - }; - - #onLoaded = () => { - this.#loaded = true; - Swal.close(); - }; - - #resetLoadState() { - this.#loaded = false; - this.#nLoaded = 0; - } - - render() { - this.#resetLoadState(); // Reset on each render - - this.localState = { results: structuredClone(this.info.globalState.results ?? {}) }; - - this.forms = this.mapSessions(this.createForm, this.localState.results); - - let instances = {}; - this.forms.forEach(({ subject, session, form }) => { - if (!instances[`sub-${subject}`]) instances[`sub-${subject}`] = {}; - instances[`sub-${subject}`][`ses-${session}`] = form; - }); - - this.manager = new InstanceManager({ - header: "Sessions", - instanceType: "Session", - instances, - - controls: [ - { - name: "Preview", - primary: true, - onClick: async (key) => { - const { subject, session } = getInfoFromId(key); - - const results = await this.runConversions( - { stub_test: true }, - [ - { - subject, - session, - globalState: merge(this.localState, structuredClone(this.info.globalState)), - }, - ], - { title: "Running conversion preview" } - ).catch(() => {}); - - if (!results) return; - - const modal = new Modal({ - header: `Conversion Preview: ${key}`, - open: true, - onClose: () => modal.remove(), - width: "100%", - height: "100%", - }); - - const { project } = this.info.globalState; - - modal.append(new NWBFilePreview({ project: project.name, files: results, inspect: true })); - document.body.append(modal); - }, - }, - - // Only save the currently selected session - { - name: "Save", - onClick: async (id) => { - const ogCallback = this.beforeSave; - this.beforeSave = () => { - const { subject, session } = getInfoFromId(id); - - const local = this.localState.results[subject][session]; - const global = this.info.globalState.results[subject][session]; - - merge(local, global); - - this.notify(`Session ${id} metadata saved!`); - }; - await this.save(); - this.beforeSave = ogCallback; - }, - }, - ], - }); - - return this.manager; - } -} - -customElements.get("nwbguide-guided-metadata-page") || - customElements.define("nwbguide-guided-metadata-page", GuidedMetadataPage); +import { JSONSchemaForm, getSchema } from "../../../JSONSchemaForm.js"; + +import { InstanceManager } from "../../../InstanceManager.js"; +import { ManagedPage } from "./ManagedPage.js"; +import { Modal } from "../../../Modal"; + +import { validateOnChange } from "../../../../validation/index.js"; +import { + resolveGlobalOverrides, + resolveMetadata, + getInfoFromId, + drillSchemaProperties, + resolveFromPath, +} from "./utils"; + +import Swal from "sweetalert2"; +import { SimpleTable } from "../../../SimpleTable.js"; +import { onThrow } from "../../../../errors"; +import { merge } from "../../utils"; +import { NWBFilePreview } from "../../../preview/NWBFilePreview.js"; +import { header, tempPropertyKey } from "../../../forms/utils"; + +import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; +import { Button } from "../../../Button.js"; + +import globalIcon from "../../../../../assets/icons/global.svg?raw"; + +const parentTableRenderConfig = { + Electrodes: (metadata) => { + metadata.schema.description = "Download, modify, and re-upload data to change the electrode information."; + return true; + }, + Units: (metadata) => { + metadata.truncated = true; + metadata.schema.description = "Update unit information directly on your source data."; + return true; + }, +}; + +function getAggregateRequirements(path) { + const electrodeSchema = getSchema(path, this.schema); + return Object.values(electrodeSchema.properties).reduce((set, schema) => { + schema.items.required.forEach((item) => set.add(item)); + return set; + }, new Set()); +} + +const tableRenderConfig = { + "*": (metadata) => new SimpleTable(metadata), + ElectrodeColumns: function (metadata) { + const aggregateRequirements = getAggregateRequirements.call(this, ["Ecephys", "Electrodes"]); + + return new SimpleTable({ + ...metadata, + editable: { + name: (value) => !aggregateRequirements.has(value), + data_type: (_, row) => !aggregateRequirements.has(row.name), + __row_remove: (_, row) => !aggregateRequirements.has(row.name), + }, + }); + }, + UnitColumns: function (metadata) { + metadata.editable = false; + metadata.schema.description = "Update unit information directly on your source data."; + + return true; + + // const aggregateRequirements = getAggregateRequirements.call(this, ["Ecephys", "Units"]); + // + // return new SimpleTable({ + // ...metadata, + // contextOptions: { + // row: { + // add: false, + // remove: false, + // }, + // }, + // editable: { + // name: (value) => !aggregateRequirements.has(value), + // data_type: (_, row) => !aggregateRequirements.has(row.name), + // }, + // }); + }, +}; + +const imagingPlaneKey = "imaging_plane"; +const propsToIgnore = { + NWBFile: { + session_id: true, + source_script: true, + source_script_file_name: true, + identifier: true, + }, + Subject: { + subject_id: true, + }, + Ophys: { + "*": { + starting_time: true, + rate: true, + conversion: true, + offset: true, + unit: true, + control: true, + comments: true, + control_description: true, + }, + ImagingPlane: { + [imagingPlaneKey]: true, + manifold: true, + }, + TwoPhotonSeries: { + [imagingPlaneKey]: true, + format: true, + starting_frame: true, + control: true, + control_description: true, + comments: true, + resolution: true, + dimension: true, + device: true, + }, + }, + Ecephys: { + ElectricalSeries: true, + ElectricalSeriesLF: true, + ElectricalSeriesAP: true, + ElectricalSeriesNIDQ: true, + Units: { + "*": { + UnitColumns: { + "*": { + data_type: true, // Do not show data_type + }, + }, + }, + }, + }, + Icephys: true, // Always ignore icephys metadata (for now) + Behavior: true, // Always ignore behavior metadata (for now) + "ndx-dandi-icephys": true, +}; + +import { preprocessMetadataSchema } from "../../../../../../../schemas/base-metadata.schema"; +import { + createTable, + getEditableItems, + isPatternProperties, + isAdditionalProperties, +} from "../../../JSONSchemaInput.js"; +import { html } from "lit"; + +export class GuidedMetadataPage extends ManagedPage { + constructor(...args) { + super(...args); + this.style.height = "100%"; // Fix main section + } + + beforeSave = () => { + merge(this.localState.results, this.info.globalState.results); + }; + + form; + + #globalButton = new Button({ + icon: globalIcon, + label: "Edit Default Metadata", + onClick: () => { + this.#globalModal.form.results = structuredClone(this.info.globalState.project); + this.#globalModal.open = true; + }, + }); + + workflow = { + multiple_sessions: { + elements: [this.#globalButton], + }, + }; + + header = { + controls: [this.#globalButton], + subtitle: "Edit all metadata for this conversion at the session level", + }; + + footer = { + onNext: async () => { + await this.save(); // Save in case the conversion fails + for (let { form } of this.forms) await form.validate(); // Will throw an error in the callback + return this.to(1); // Will trigger preview conversion if necessary + }, + }; + + #globalModal = null; + + connectedCallback() { + super.connectedCallback(); + + // 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: "Default Metadata", + propsToRemove: toRemove, + schema, + hasInstances: true, + mergeFunction: function (globalResolved, globals) { + merge(globalResolved, globals); + return resolveGlobalOverrides(this.subject, globals); + }, + formProps: { + validateOnChange, + }, + })); + document.body.append(modal); + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this.#globalModal) this.#globalModal.remove(); + } + + createForm = ({ subject, session, info }) => { + const hasMultipleSessions = this.workflow.multiple_sessions.value; + + // const results = createResults({ subject, info }, this.info.globalState); + + const { globalState } = this.info; + + const results = info.metadata; // Edited form info + + // Define the appropriate global metadata to fill empty values in the form + const aggregateGlobalMetadata = resolveGlobalOverrides(subject, globalState, hasMultipleSessions); + + // Define the correct instance identifier + const instanceId = `sub-${subject}/ses-${session}`; + + // Ignore specific metadata in the form by removing their schema value + const schema = preprocessMetadataSchema(globalState.schema.metadata[subject][session]); + delete schema.description; + + resolveMetadata(subject, session, globalState); + + const additionalPropertiesToRetitle = ["Ophys.ImageSegmentation"]; + + const patternPropsToRetitle = ["Ophys.Fluorescence", "Ophys.DfOverF", "Ophys.SegmentationImages"]; + + console.log("schema", structuredClone(schema), structuredClone(results)); + + const ophys = schema.properties.Ophys; + if (ophys) { + drillSchemaProperties( + schema, + (path, schema, target, isPatternProperties, parentSchema) => { + if (path[0] === "Ophys") { + const name = path.slice(-1)[0]; + + if (isPatternProperties) schema.minItems = schema.maxItems = null; // Do not allow more than on the results + + if (schema.type === "array") { + if (name !== "Device" && target) { + // Set most Ophys tables to have minItems / maxItems equal (i.e. no editing possible) + if (name in target) schema.minItems = schema.maxItems = null; + // Remove Ophys property requirement if left initially undefined + else { + target[name] = []; // Initialize empty array + if (parentSchema.required.includes(name)) + parentSchema.required = parentSchema.required.filter((n) => n !== name); + } + } + } + } + }, + results + ); + } + + console.log("schema", structuredClone(schema), structuredClone(results)); + + delete results.__generated; // Ignore generated results. NOTE: See if this fails + + // Create the form + const form = new JSONSchemaForm({ + identifier: instanceId, + schema, + results, + globals: aggregateGlobalMetadata, + + ignore: propsToIgnore, + onOverride: (name) => { + this.notify(`${header(name)} has been overridden with a global value.`, "warning", 3000); + }, + + transformErrors: (error) => { + // JSON Schema Exceptions + if (error.message.includes('does not conform to the "date-time" format.')) return false; + if (error.message.includes('not allowed to have the additional property "Ecephys".')) return false; // NOTE: Remove after including Ecephys metadata + }, + + groups: [ + { + name: "Subject Age", + properties: [ + ["Subject", "age"], + ["Subject", "date_of_birth"], + ], + link: true, + conditional: true, + }, + { + name: "Institutional Info", + properties: [ + ["NWBFile", "institution"], + ["NWBFile", "lab"], + ], + }, + + // For C. Elegans + { + properties: [ + ["Subject", "sex"], + ["Subject", "species"], + ], + link: true, + }, + ], + + // deferLoading: true, + onLoaded: () => { + this.#nLoaded++; + this.#checkAllLoaded(); + }, + + onUpdate: () => (this.unsavedUpdates = "conversions"), + + validateOnChange, + onlyRequired: false, + onStatusChange: (state) => this.manager.updateState(`sub-${subject}/ses-${session}`, state), + + renderCustomHTML: function (name, inputSchema, localPath, { onUpdate, onThrow }) { + if (name === "TwoPhotonSeries" && (!this.value || !this.value.length)) return null; + if (name === "Device" && (!this.value || !this.value.length)) return null; + if (name === "ElectrodeGroup" && (!this.value || !this.value.length)) return null; + + const isAdditional = isAdditionalProperties(this.pattern); + const isPattern = isPatternProperties(this.pattern); + + if (isAdditional || isPattern) { + // One table with nested tables for each property + const data = getEditableItems(this.value, this.pattern, { name, schema: this.schema }).reduce( + (acc, { key, value }) => { + acc[key] = value; + return acc; + }, + {} + ); + + const nProps = Object.keys(data).length; + + const schemaCopy = { ...inputSchema }; + + if (additionalPropertiesToRetitle.includes(this.form.base.join("."))) { + inputSchema.title = ""; + + return Object.entries(data).map(([name, value]) => { + const mockInput = { + schema: { + type: "array", + items: { + type: "object", + additionalProperties: true, + }, + }, + renderTable: this.renderTable, + value, + form: { + ignore: this.form.ignore, + }, + }; + + const table = createTable.call(mockInput, [...localPath], { + onUpdate: (localPath, value) => + onUpdate([name, ...localPath], value, true, { + willTimeout: false, + onError: (e) => e, + onWarning: (e) => e, + }), + onThrow: onThrow, + }); + + return html` +
+

${header(name)}

+ ${table} +
+ `; + }); + } + + if (patternPropsToRetitle.includes(this.form.base.join("."))) { + inputSchema.title = "Plane Metadata
"; + + return html`
+ ${Object.entries(data) + .map(([name, value]) => { + const createNestedTable = (value, pattern, schema) => { + const mockInput = { + schema: { + type: "object", + items: schema, + + // Transfer a subset of item schema values + minItems: schema.minItems, + maxItems: schema.maxItems, + }, + + renderTable: this.renderTable, + value, + pattern: pattern, + form: { + ignore: this.form.ignore, + }, + }; + + return html` +
+ ${nProps > 1 ? html`

${header(name)}

` : ""} + ${createTable.call(mockInput, [...localPath], { + overrides: { + schema: { + items: { + order: ["name", "description"], + additionalProperties: false, + }, + }, + ignore: { + [tempPropertyKey]: true, + }, + }, + onUpdate: (localPath, value) => + onUpdate([name, ...localPath], value, true, { + willTimeout: false, + onError: (e) => e, + onWarning: (e) => e, + }), + onThrow: onThrow, + })} +
+ `; + }; + + if (isAdditional) { + const data = value.reduce((acc, item) => { + const name = item.name; + acc[name] = item; + return acc; + }, {}); + + return createNestedTable(data, undefined, { + type: "object", + items: { + type: "object", + additionalProperties: true, + }, + }); + } + + return Object.entries(schemaCopy.patternProperties).map(([pattern, schema]) => { + return createNestedTable(value, pattern, schema); + }); + }) + .flat()} +
`; + } + } + }, + + renderTable: function (name, metadata, fullPath) { + const updatedSchema = structuredClone(metadata.schema); + metadata.schema = updatedSchema; + + const parentName = fullPath[fullPath.length - 1]; + + const tableConfig = + tableRenderConfig[name] ?? parentTableRenderConfig[parentName] ?? tableRenderConfig["*"] ?? true; + if (typeof tableConfig === "function") + return tableConfig.call(form, metadata, [...fullPath, name], this); + else return tableConfig; + }, + onThrow, + }); + + return { + subject, + session, + form, + }; + }; + + #nLoaded = 0; + #loaded = false; + + #checkAllLoaded = () => { + if (this.#nLoaded === this.forms.length) this.#onLoaded(); + }; + + #onLoaded = () => { + this.#loaded = true; + Swal.close(); + }; + + #resetLoadState() { + this.#loaded = false; + this.#nLoaded = 0; + } + + render() { + this.#resetLoadState(); // Reset on each render + + this.localState = { results: structuredClone(this.info.globalState.results ?? {}) }; + + this.forms = this.mapSessions(this.createForm, this.localState.results); + + let instances = {}; + this.forms.forEach(({ subject, session, form }) => { + if (!instances[`sub-${subject}`]) instances[`sub-${subject}`] = {}; + instances[`sub-${subject}`][`ses-${session}`] = form; + }); + + this.manager = new InstanceManager({ + header: "Sessions", + instanceType: "Session", + instances, + + controls: [ + { + name: "Preview", + primary: true, + onClick: async (key) => { + const { subject, session } = getInfoFromId(key); + + const results = await this.runConversions( + { stub_test: true }, + [ + { + subject, + session, + globalState: merge(this.localState, structuredClone(this.info.globalState)), + }, + ], + { title: "Running conversion preview" } + ).catch(() => {}); + + if (!results) return; + + const modal = new Modal({ + header: `Conversion Preview: ${key}`, + open: true, + onClose: () => modal.remove(), + width: "100%", + height: "100%", + }); + + const { project } = this.info.globalState; + + modal.append(new NWBFilePreview({ project: project.name, files: results, inspect: true })); + document.body.append(modal); + }, + }, + + // Only save the currently selected session + { + name: "Save", + onClick: async (id) => { + const ogCallback = this.beforeSave; + this.beforeSave = () => { + const { subject, session } = getInfoFromId(id); + + const local = this.localState.results[subject][session]; + const global = this.info.globalState.results[subject][session]; + + merge(local, global); + + this.notify(`Session ${id} metadata saved!`); + }; + await this.save(); + this.beforeSave = ogCallback; + }, + }, + ], + }); + + return this.manager; + } +} + +customElements.get("nwbguide-guided-metadata-page") || + customElements.define("nwbguide-guided-metadata-page", GuidedMetadataPage); diff --git a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js index 0a2d26aec..4fa48857a 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedPathExpansion.js @@ -1,449 +1,449 @@ -import { html } from "lit"; -import { Page } from "../../Page.js"; - -// For Multi-Select Form -import { JSONSchemaForm, getSchema } from "../../../JSONSchemaForm.js"; -import { run } from "../options/utils.js"; -import { onThrow } from "../../../../errors"; - -import pathExpansionSchema from "../../../../../../../schemas/json/path-expansion.schema.json" assert { type: "json" }; -import { merge } from "../../utils"; -import { List } from "../../../List"; -import { fs } from "../../../../electron/index.js"; -import { Button } from "../../../Button.js"; -import { Modal } from "../../../Modal"; -import { header } from "../../../forms/utils"; - -import autocompleteIcon from "../../../../../assets/icons/inspect.svg?raw"; - -const propOrder = ["path", "subject_id", "session_id"]; - -export async function autocompleteFormatString(path) { - let notification; - - const { base_directory } = path.reduce((acc, key) => acc[key] ?? {}, this.form.resolved); - - const schema = getSchema(path, this.info.globalState.schema.source_data); - - const isFile = "file_path" in schema.properties; - const pathType = isFile ? "file" : "directory"; - - const description = isFile ? schema.properties.file_path.description : schema.properties.folder_path.description; - - const notify = (message, type) => { - if (notification) this.dismiss(notification); - return (notification = this.notify(message, type)); - }; - - if (!base_directory) { - const message = `Please fill out the base directory for ${header(path[0])} before attempting auto-completion.`; - notify(message, "error"); - throw new Error(message); - } - - const modal = new Modal({ - header: "Autocomplete Format String", - }); - - const content = document.createElement("div"); - Object.assign(content.style, { - padding: "25px", - }); - - const form = new JSONSchemaForm({ - validateEmptyValues: false, - schema: { - type: "object", - properties: { - path: { - type: "string", - title: `Example ${isFile ? "File" : "Folder"}`, - format: pathType, - description: description ?? `Provide an example ${pathType} for the selected interface`, - }, - subject_id: { - type: "string", - description: "The subject ID in the above entry", - }, - session_id: { - type: "string", - description: "The session ID in the above entry", - }, - }, - required: propOrder, - order: propOrder, - }, - validateOnChange: async (name, parent) => { - const value = parent[name]; - - if (name === "path") { - const toUpdate = ["subject_id", "session_id"]; - toUpdate.forEach((key) => form.getFormElement([key]).requestUpdate()); - - if (value) { - if (fs.lstatSync(value).isSymbolicLink()) - return [ - { - type: "error", - message: "This feature does not support symbolic links. Please provide a valid path.", - }, - ]; - - if (base_directory) { - if (!value.includes(base_directory)) - return [ - { - type: "error", - message: - "The provided path must include the base directory.
This is likely due to the target being contained in a symlink, which is unsupported by this feature.", - }, - ]; - } - - const errors = []; - for (let key in parent) { - if (key === name) continue; - if (!value.includes(parent[key])) - errors.push({ - type: "error", - message: `${header(name)} not found in the updated path.`, - }); - } - } - } else { - if (!parent.path) return; - if (!value) return; - - if (!parent.path.includes(value)) - return [ - { - type: "error", - message: `${header(name)} not found in the provided path.`, - }, - ]; - } - }, - }); - - content.append(form); - modal.append(content); - - modal.onClose = async () => notify("Format String Path was not completed.", "error"); - - return new Promise((resolve) => { - const button = new Button({ - label: "Submit", - primary: true, - onClick: async () => { - await form.validate().catch((e) => { - notify(e.message, "error"); - throw e; - }); - - const results = await run("locate/autocomplete", { - base_directory, - additional_metadata: {}, - ...form.results, - }); - const input = this.form.getFormElement([...path, "format_string_path"]); - input.updateData(results.format_string); - this.save(); - resolve(results.format_string); - }, - }); - - modal.footer = button; - - modal.open = true; - - document.body.append(modal); - }).finally(() => { - modal.remove(); - }); -} - -export class GuidedPathExpansionPage extends Page { - #notification; - - constructor(...args) { - super(...args); - } - - header = { - subtitle: "Automatic source data detection for multiple subjects / sessions", - }; - - #initialize = () => (this.localState = merge(this.info.globalState.structure, { results: {} })); - - workflow = { - subject_id: {}, - session_id: {}, - base_directory: {}, - locate_data: { - skip: () => { - this.#initialize(); - const globalState = this.info.globalState; - merge({ structure: this.localState }, globalState); // Merge the actual entries into the structure - - // Force single subject/session if not keeping existing data - // if (!globalState.results) { - - const subject_id = this.workflow.subject_id.value; - const session_id = this.workflow.session_id.value; - - // Map existing results to new subject information (if available) - const existingResults = Object.values(Object.values(globalState.results ?? {})[0] ?? {})[0] ?? {}; - - const existingMetadata = existingResults.metadata ?? {}; - const existingSourceData = existingResults.source_data; - - const source_data = {}; - for (let key in globalState.interfaces) { - const existing = existingSourceData?.[key]; - if (existing) source_data[key] = existing ?? {}; - } - - const sub_id = subject_id ?? ""; - const ses_id = session_id ?? ""; - - // Skip if results already exist without manual IDs - if ((!subject_id || !session_id) && globalState.results) return; - // Otherwise reset the results to the new subject/session - else { - globalState.results = {}; - - globalState.results[sub_id] = {}; - - const metadata = structuredClone(existingMetadata); - if (!metadata.NWBFile) metadata.NWBFile = {}; - if (!metadata.Subject) metadata.Subject = {}; - metadata.NWBFile.session_id = ses_id; - metadata.Subject.subject_id = sub_id; - - globalState.results[sub_id][ses_id] = { source_data, metadata }; - } - - this.save({}, false); // Ensure this structure is saved - }, - }, - }; - - beforeSave = async () => { - const globalState = this.info.globalState; - merge({ structure: this.localState }, globalState); // Merge the actual entries into the structure - - const structure = globalState.structure.results; - - await this.form.validate(); - - const globalBaseDirectory = this.workflow.base_directory.value; - - const finalStructure = {}; - for (let key in structure) { - const entry = { ...structure[key] }; - const fstring = entry.format_string_path; - if (!fstring) continue; - if (fstring.split(".").length > 1) entry.file_path = fstring; - else entry.folder_path = fstring; - delete entry.format_string_path; - - if (!entry.base_directory && globalBaseDirectory) entry.base_directory = globalBaseDirectory; - - finalStructure[key] = entry; - } - - if (Object.keys(finalStructure).length === 0) { - const message = - "Please configure at least one interface.
Otherwise, revisit Pipeline Workflow to update your configuration."; - this.#notification = this.notify(message, "error"); - throw message; - } - - const results = await run(`locate`, finalStructure, { title: "Locating Data" }).catch((error) => { - this.notify(error.message, "error"); - throw error; - }); - - const subjects = Object.keys(results); - if (subjects.length === 0) { - if (this.#notification) this.dismiss(this.#notification); - const message = "No subjects found with the current configuration. Please try again."; - this.#notification = this.notify(message, "error"); - throw message; - } - - // globalState.results = {} // Clear existing results - - // Save an overall results object organized by subject and session - merge({ results }, globalState); - - const globalResults = globalState.results; - - for (let sub in globalResults) { - const subRef = results[sub]; - if (!subRef) - delete globalResults[sub]; // Delete removed subjects - else { - for (let ses in globalResults[sub]) { - const sesRef = subRef[ses]; - - if (!sesRef) - delete globalResults[sub][ses]; // Delete removed sessions - else { - const globalSesRef = globalResults[sub][ses]; - - for (let name in globalSesRef.source_data) { - if (!sesRef.source_data[name]) delete globalSesRef.source_data[name]; // Delete removed interfaces - } - } - } - - if (Object.keys(globalResults[sub]).length === 0) delete globalResults[sub]; // Delete empty subjects - } - } - }; - - footer = { - onNext: async () => { - await this.save(); // Save in case the request fails - - await this.form.validate(); - - return this.to(1); - }, - }; - - localState = {}; - - render() { - const structureState = this.#initialize(); - - // Require properties for all sources - const generatedSchema = { type: "object", properties: {}, additionalProperties: false }; - const controls = {}; - - const baseDirectory = this.workflow.base_directory.value; - const globals = (structureState.globals = {}); - - for (let key in this.info.globalState.interfaces) { - generatedSchema.properties[key] = { type: "object", ...pathExpansionSchema }; - - if (baseDirectory) globals[key] = { base_directory: baseDirectory }; - - controls[key] = { - format_string_path: [ - new Button({ - label: "Autocomplete", - icon: autocompleteIcon, - buttonStyles: { - width: "max-content", - }, - onClick: async () => autocompleteFormatString.call(this, [key]), - }), - ], - }; - } - structureState.schema = generatedSchema; - - const form = (this.form = new JSONSchemaForm({ - ...structureState, - onThrow, - validateEmptyValues: null, - - controls, - - // NOTE: These are custom coupled form inputs - onUpdate: (path, value) => { - this.unsavedUpdates = "conversions"; - - const parentPath = [...path]; - const name = parentPath.pop(); - - if (name === "base_directory") { - form.getFormElement([...parentPath, "base_directory"]).value = value; // Update value pre-emptively - const input = form.getFormElement([...parentPath, "format_string_path"]); - if (input.value) input.updateData(input.value, true); - } - }, - validateOnChange: async (name, parent, parentPath) => { - const value = parent[name]; - - if (fs) { - const baseDir = form.getFormElement([...parentPath, "base_directory"]); - if (name === "format_string_path") { - if (value && baseDir && !baseDir.value) { - return [ - { - message: html`A base directory must be provided to locate your files.`, - type: "error", - }, - ]; - } - - const base_directory = [...parentPath, "base_directory"].reduce( - (acc, key) => acc[key], - this.form.resolved - ); - - if (!base_directory) return true; // Do not calculate if base is not found - - const entry = { base_directory }; - - if (value.split(".").length > 1) entry.file_path = value; - else entry.folder_path = value; - - const interfaceName = parentPath.slice(-1)[0]; - - const results = await run(`locate`, { [interfaceName]: entry }, { swal: false }).catch( - (error) => { - this.notify(error.message, "error"); - throw error; - } - ); - - const resolved = []; - - for (let sub in results) { - for (let ses in results[sub]) { - const source_data = results[sub][ses].source_data[interfaceName]; - const path = source_data.file_path ?? source_data.folder_path; - resolved.push(path.slice(base_directory.length + 1)); - } - } - - if (resolved.length === 0) - return [ - { - message: html`No source files found using the provided information.`, - type: "warning", - }, - ]; - - return [ - { - message: html`

Source Files Found

- ${base_directory} - ${new List({ - items: resolved.map((path) => { - return { value: path }; - }), - editable: false, - })}`, - type: "info", - }, - ]; - } - } - }, - })); - - form.style.width = "100%"; - - return form; - } -} - -customElements.get("nwbguide-guided-pathexpansion-page") || - customElements.define("nwbguide-guided-pathexpansion-page", GuidedPathExpansionPage); +import { html } from "lit"; +import { Page } from "../../Page.js"; + +// For Multi-Select Form +import { JSONSchemaForm, getSchema } from "../../../JSONSchemaForm.js"; +import { run } from "../options/utils.js"; +import { onThrow } from "../../../../errors"; + +import pathExpansionSchema from "../../../../../../../schemas/json/path-expansion.schema.json" assert { type: "json" }; +import { merge } from "../../utils"; +import { List } from "../../../List"; +import { fs } from "../../../../electron/index.js"; +import { Button } from "../../../Button.js"; +import { Modal } from "../../../Modal"; +import { header } from "../../../forms/utils"; + +import autocompleteIcon from "../../../../../assets/icons/inspect.svg?raw"; + +const propOrder = ["path", "subject_id", "session_id"]; + +export async function autocompleteFormatString(path) { + let notification; + + const { base_directory } = path.reduce((acc, key) => acc[key] ?? {}, this.form.resolved); + + const schema = getSchema(path, this.info.globalState.schema.source_data); + + const isFile = "file_path" in schema.properties; + const pathType = isFile ? "file" : "directory"; + + const description = isFile ? schema.properties.file_path.description : schema.properties.folder_path.description; + + const notify = (message, type) => { + if (notification) this.dismiss(notification); + return (notification = this.notify(message, type)); + }; + + if (!base_directory) { + const message = `Please fill out the base directory for ${header(path[0])} before attempting auto-completion.`; + notify(message, "error"); + throw new Error(message); + } + + const modal = new Modal({ + header: "Autocomplete Format String", + }); + + const content = document.createElement("div"); + Object.assign(content.style, { + padding: "25px", + }); + + const form = new JSONSchemaForm({ + validateEmptyValues: false, + schema: { + type: "object", + properties: { + path: { + type: "string", + title: `Example ${isFile ? "File" : "Folder"}`, + format: pathType, + description: description ?? `Provide an example ${pathType} for the selected interface`, + }, + subject_id: { + type: "string", + description: "The subject ID in the above entry", + }, + session_id: { + type: "string", + description: "The session ID in the above entry", + }, + }, + required: propOrder, + order: propOrder, + }, + validateOnChange: async (name, parent) => { + const value = parent[name]; + + if (name === "path") { + const toUpdate = ["subject_id", "session_id"]; + toUpdate.forEach((key) => form.getFormElement([key]).requestUpdate()); + + if (value) { + if (fs.lstatSync(value).isSymbolicLink()) + return [ + { + type: "error", + message: "This feature does not support symbolic links. Please provide a valid path.", + }, + ]; + + if (base_directory) { + if (!value.includes(base_directory)) + return [ + { + type: "error", + message: + "The provided path must include the base directory.
This is likely due to the target being contained in a symlink, which is unsupported by this feature.", + }, + ]; + } + + const errors = []; + for (let key in parent) { + if (key === name) continue; + if (!value.includes(parent[key])) + errors.push({ + type: "error", + message: `${header(name)} not found in the updated path.`, + }); + } + } + } else { + if (!parent.path) return; + if (!value) return; + + if (!parent.path.includes(value)) + return [ + { + type: "error", + message: `${header(name)} not found in the provided path.`, + }, + ]; + } + }, + }); + + content.append(form); + modal.append(content); + + modal.onClose = async () => notify("Format String Path was not completed.", "error"); + + return new Promise((resolve) => { + const button = new Button({ + label: "Submit", + primary: true, + onClick: async () => { + await form.validate().catch((e) => { + notify(e.message, "error"); + throw e; + }); + + const results = await run("locate/autocomplete", { + base_directory, + additional_metadata: {}, + ...form.results, + }); + const input = this.form.getFormElement([...path, "format_string_path"]); + input.updateData(results.format_string); + this.save(); + resolve(results.format_string); + }, + }); + + modal.footer = button; + + modal.open = true; + + document.body.append(modal); + }).finally(() => { + modal.remove(); + }); +} + +export class GuidedPathExpansionPage extends Page { + #notification; + + constructor(...args) { + super(...args); + } + + header = { + subtitle: "Automatic source data detection for multiple subjects / sessions", + }; + + #initialize = () => (this.localState = merge(this.info.globalState.structure, { results: {} })); + + workflow = { + subject_id: {}, + session_id: {}, + base_directory: {}, + locate_data: { + skip: () => { + this.#initialize(); + const globalState = this.info.globalState; + merge({ structure: this.localState }, globalState); // Merge the actual entries into the structure + + // Force single subject/session if not keeping existing data + // if (!globalState.results) { + + const subject_id = this.workflow.subject_id.value; + const session_id = this.workflow.session_id.value; + + // Map existing results to new subject information (if available) + const existingResults = Object.values(Object.values(globalState.results ?? {})[0] ?? {})[0] ?? {}; + + const existingMetadata = existingResults.metadata ?? {}; + const existingSourceData = existingResults.source_data; + + const source_data = {}; + for (let key in globalState.interfaces) { + const existing = existingSourceData?.[key]; + if (existing) source_data[key] = existing ?? {}; + } + + const sub_id = subject_id ?? ""; + const ses_id = session_id ?? ""; + + // Skip if results already exist without manual IDs + if ((!subject_id || !session_id) && globalState.results) return; + // Otherwise reset the results to the new subject/session + else { + globalState.results = {}; + + globalState.results[sub_id] = {}; + + const metadata = structuredClone(existingMetadata); + if (!metadata.NWBFile) metadata.NWBFile = {}; + if (!metadata.Subject) metadata.Subject = {}; + metadata.NWBFile.session_id = ses_id; + metadata.Subject.subject_id = sub_id; + + globalState.results[sub_id][ses_id] = { source_data, metadata }; + } + + this.save({}, false); // Ensure this structure is saved + }, + }, + }; + + beforeSave = async () => { + const globalState = this.info.globalState; + merge({ structure: this.localState }, globalState); // Merge the actual entries into the structure + + const structure = globalState.structure.results; + + await this.form.validate(); + + const globalBaseDirectory = this.workflow.base_directory.value; + + const finalStructure = {}; + for (let key in structure) { + const entry = { ...structure[key] }; + const fstring = entry.format_string_path; + if (!fstring) continue; + if (fstring.split(".").length > 1) entry.file_path = fstring; + else entry.folder_path = fstring; + delete entry.format_string_path; + + if (!entry.base_directory && globalBaseDirectory) entry.base_directory = globalBaseDirectory; + + finalStructure[key] = entry; + } + + if (Object.keys(finalStructure).length === 0) { + const message = + "Please configure at least one interface.
Otherwise, revisit Pipeline Workflow to update your configuration."; + this.#notification = this.notify(message, "error"); + throw message; + } + + const results = await run(`locate`, finalStructure, { title: "Locating Data" }).catch((error) => { + this.notify(error.message, "error"); + throw error; + }); + + const subjects = Object.keys(results); + if (subjects.length === 0) { + if (this.#notification) this.dismiss(this.#notification); + const message = "No subjects found with the current configuration. Please try again."; + this.#notification = this.notify(message, "error"); + throw message; + } + + // globalState.results = {} // Clear existing results + + // Save an overall results object organized by subject and session + merge({ results }, globalState); + + const globalResults = globalState.results; + + for (let sub in globalResults) { + const subRef = results[sub]; + if (!subRef) + delete globalResults[sub]; // Delete removed subjects + else { + for (let ses in globalResults[sub]) { + const sesRef = subRef[ses]; + + if (!sesRef) + delete globalResults[sub][ses]; // Delete removed sessions + else { + const globalSesRef = globalResults[sub][ses]; + + for (let name in globalSesRef.source_data) { + if (!sesRef.source_data[name]) delete globalSesRef.source_data[name]; // Delete removed interfaces + } + } + } + + if (Object.keys(globalResults[sub]).length === 0) delete globalResults[sub]; // Delete empty subjects + } + } + }; + + footer = { + onNext: async () => { + await this.save(); // Save in case the request fails + + await this.form.validate(); + + return this.to(1); + }, + }; + + localState = {}; + + render() { + const structureState = this.#initialize(); + + // Require properties for all sources + const generatedSchema = { type: "object", properties: {}, additionalProperties: false }; + const controls = {}; + + const baseDirectory = this.workflow.base_directory.value; + const globals = (structureState.globals = {}); + + for (let key in this.info.globalState.interfaces) { + generatedSchema.properties[key] = { type: "object", ...pathExpansionSchema }; + + if (baseDirectory) globals[key] = { base_directory: baseDirectory }; + + controls[key] = { + format_string_path: [ + new Button({ + label: "Autocomplete", + icon: autocompleteIcon, + buttonStyles: { + width: "max-content", + }, + onClick: async () => autocompleteFormatString.call(this, [key]), + }), + ], + }; + } + structureState.schema = generatedSchema; + + const form = (this.form = new JSONSchemaForm({ + ...structureState, + onThrow, + validateEmptyValues: null, + + controls, + + // NOTE: These are custom coupled form inputs + onUpdate: (path, value) => { + this.unsavedUpdates = "conversions"; + + const parentPath = [...path]; + const name = parentPath.pop(); + + if (name === "base_directory") { + form.getFormElement([...parentPath, "base_directory"]).value = value; // Update value pre-emptively + const input = form.getFormElement([...parentPath, "format_string_path"]); + if (input.value) input.updateData(input.value, true); + } + }, + validateOnChange: async (name, parent, parentPath) => { + const value = parent[name]; + + if (fs) { + const baseDir = form.getFormElement([...parentPath, "base_directory"]); + if (name === "format_string_path") { + if (value && baseDir && !baseDir.value) { + return [ + { + message: html`A base directory must be provided to locate your files.`, + type: "error", + }, + ]; + } + + const base_directory = [...parentPath, "base_directory"].reduce( + (acc, key) => acc[key], + this.form.resolved + ); + + if (!base_directory) return true; // Do not calculate if base is not found + + const entry = { base_directory }; + + if (value.split(".").length > 1) entry.file_path = value; + else entry.folder_path = value; + + const interfaceName = parentPath.slice(-1)[0]; + + const results = await run(`locate`, { [interfaceName]: entry }, { swal: false }).catch( + (error) => { + this.notify(error.message, "error"); + throw error; + } + ); + + const resolved = []; + + for (let sub in results) { + for (let ses in results[sub]) { + const source_data = results[sub][ses].source_data[interfaceName]; + const path = source_data.file_path ?? source_data.folder_path; + resolved.push(path.slice(base_directory.length + 1)); + } + } + + if (resolved.length === 0) + return [ + { + message: html`No source files found using the provided information.`, + type: "warning", + }, + ]; + + return [ + { + message: html`

Source Files Found

+ ${base_directory} + ${new List({ + items: resolved.map((path) => { + return { value: path }; + }), + editable: false, + })}`, + type: "info", + }, + ]; + } + } + }, + })); + + form.style.width = "100%"; + + return form; + } +} + +customElements.get("nwbguide-guided-pathexpansion-page") || + customElements.define("nwbguide-guided-pathexpansion-page", GuidedPathExpansionPage); diff --git a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js index cb6487423..f3876100a 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -1,384 +1,384 @@ -import Swal from "sweetalert2"; -import { isStorybook } from "../../../../dependencies/globals"; -import { JSONSchemaForm } from "../../../JSONSchemaForm.js"; -import { InstanceManager } from "../../../InstanceManager.js"; -import { ManagedPage } from "./ManagedPage.js"; -import { onThrow } from "../../../../errors"; -import { merge, sanitize } from "../../utils"; -import preprocessSourceDataSchema from "../../../../../../../schemas/source-data.schema"; - -import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; -import { header } from "../../../forms/utils"; -import { Button } from "../../../Button.js"; - -import globalIcon from "../../../../../assets/icons/global.svg?raw"; - -import { baseUrl } from "../../../../server/globals"; - -import { run } from "../options/utils.js"; -import { getInfoFromId } from "./utils"; -import { Modal } from "../../../Modal"; - -const propsToIgnore = { - "*": { - verbose: true, - es_key: true, - exclude_shanks: true, - load_sync_channel: true, - stream_id: true, // NOTE: May be desired for other interfaces - nsx_override: true, - combined: true, - plane_no: true, - }, -}; - -export class GuidedSourceDataPage extends ManagedPage { - constructor(...args) { - super(...args); - this.style.height = "100%"; // Fix main section - } - - beforeSave = () => { - merge(this.localState, this.info.globalState); - }; - - #globalButton = new Button({ - icon: globalIcon, - label: "Edit Default Values", - onClick: () => { - this.#globalModal.form.results = structuredClone(this.info.globalState.project.SourceData ?? {}); - this.#globalModal.open = true; - }, - }); - - header = { - controls: [this.#globalButton], - subtitle: - "Specify the file and folder locations on your local system for each interface, as well as any additional details that might be required.", - }; - - workflow = { - multiple_sessions: { - elements: [this.#globalButton], - }, - }; - - footer = { - onNext: async () => { - await this.save(); // Save in case the conversion fails - - for (let { form } of this.forms) await form.validate(); // Will throw an error in the callback - - // const previousResults = this.info.globalState.metadata.results - - let stillFireSwal = true; - const fireSwal = () => { - Swal.fire({ - title: "Getting metadata for source data", - html: "Please wait...", - allowEscapeKey: false, - allowOutsideClick: false, - heightAuto: false, - backdrop: "rgba(0,0,0, 0.4)", - timerProgressBar: false, - didOpen: () => { - Swal.showLoading(); - }, - }); - }; - - setTimeout(() => { - if (stillFireSwal) fireSwal(); - }); - - await Promise.all( - Object.values(this.forms).map(async ({ subject, session, form }) => { - const info = this.info.globalState.results[subject][session]; - - // NOTE: This clears all user-defined results - const result = await fetch(`${baseUrl}/neuroconv/metadata`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - source_data: sanitize(structuredClone(form.resolved)), // Use resolved values, including global source data - interfaces: this.info.globalState.interfaces, - }), - }) - .then((res) => res.json()) - .catch((error) => { - Swal.close(); - stillFireSwal = false; - this.notify(`Critical Error: ${error.message}`, "error", 4000); - throw error; - }); - - Swal.close(); - - if (isStorybook) return; - - if (result.message) { - const [type, ...splitText] = result.message.split(":"); - const text = splitText.length - ? splitText.join(":").replaceAll("<", "<").replaceAll(">", ">") - : result.traceback - ? `
${result.traceback.trim().split("\n").slice(-2)[0].trim()}
` - : ""; - - const message = `

Request Failed

${type}

${text}

`; - this.notify(message, "error"); - throw result; - } - - const { results: metadata, schema } = result; - - // Merge arrays from generated pipeline data - if (info.metadata.__generated) { - const generated = info.metadata.__generated; - info.metadata = merge(merge(generated, metadata, { arrays: true }), info.metadata); - } - - // Merge new results with old metadata - else merge(metadata, info.metadata); - - // Mirror structure with metadata schema - const schemaGlobal = this.info.globalState.schema; - if (!schemaGlobal.metadata) schemaGlobal.metadata = {}; - if (!schemaGlobal.metadata[subject]) schemaGlobal.metadata[subject] = {}; - schemaGlobal.metadata[subject][session] = schema; - }) - ); - - await this.save(undefined, false); // Just save new raw values - - return this.to(1); - }, - }; - - createForm = ({ subject, session, info }) => { - const hasMultipleSessions = this.workflow.multiple_sessions.value; - - const instanceId = `sub-${subject}/ses-${session}`; - - const schema = structuredClone(this.info.globalState.schema.source_data); - delete schema.description; - - const form = new JSONSchemaForm({ - identifier: instanceId, - schema: preprocessSourceDataSchema(schema), - results: info.source_data, - emptyMessage: "No source data required for this session.", - ignore: propsToIgnore, - globals: hasMultipleSessions ? this.info.globalState.project.SourceData : undefined, - onOverride: (name) => { - this.notify(`${header(name)} has been overridden with a global value.`, "warning", 3000); - }, - // onlyRequired: true, - onUpdate: () => (this.unsavedUpdates = "conversions"), - onStatusChange: (state) => this.manager.updateState(instanceId, state), - onThrow, - }); - - form.style.height = "100%"; - - return { - subject, - session, - form, - }; - }; - - #globalModal = null; - - connectedCallback() { - super.connectedCallback(); - - const schema = structuredClone(this.info.globalState.schema.source_data); - delete schema.description; - - const modal = (this.#globalModal = createGlobalFormModal.call(this, { - header: "Global Source Data", - propsToRemove: { - "*": { - ...propsToIgnore["*"], - folder_path: true, - file_path: true, - // NOTE: Still keeping plural path specifications for now - }, - }, - key: "SourceData", - schema, - hasInstances: true, - })); - document.body.append(modal); - } - - disconnectedCallback() { - super.disconnectedCallback(); - if (this.#globalModal) this.#globalModal.remove(); - } - - updated() { - const dashboard = document.querySelector("nwb-dashboard"); - const page = dashboard.page; - } - - render() { - this.localState = { results: structuredClone(this.info.globalState.results ?? {}) }; - - this.forms = this.mapSessions(this.createForm, this.localState.results); - - let instances = {}; - this.forms.forEach(({ subject, session, form }) => { - if (!instances[`sub-${subject}`]) instances[`sub-${subject}`] = {}; - instances[`sub-${subject}`][`ses-${session}`] = form; - }); - - this.manager = new InstanceManager({ - header: "Sessions", - // instanceType: 'Session', - instances, - controls: [ - { - name: "Check Alignment", - primary: true, - onClick: async (id) => { - const { globalState } = this.info; - - const { subject, session } = getInfoFromId(id); - - const souceCopy = structuredClone(globalState.results[subject][session].source_data); - - const sessionInfo = { - interfaces: globalState.interfaces, - source_data: merge(globalState.project.SourceData, souceCopy), - }; - - const results = await run("alignment", sessionInfo, { - title: "Checking Alignment", - message: "Please wait...", - }); - - const header = document.createElement("div"); - const h2 = document.createElement("h2"); - Object.assign(h2.style, { - marginBottom: "10px", - }); - h2.innerText = `Alignment Preview: ${subject}/${session}`; - const warning = document.createElement("small"); - warning.innerHTML = - "Warning: This is just a preview. We do not currently have the features implemented to change the alignment of your interfaces."; - header.append(h2, warning); - - const modal = new Modal({ - header, - }); - - document.body.append(modal); - - const content = document.createElement("div"); - Object.assign(content.style, { - display: "flex", - flexDirection: "column", - gap: "20px", - padding: "20px", - }); - - modal.append(content); - - const flatTimes = Object.values(results) - .map((interfaceTimestamps) => { - return [interfaceTimestamps[0], interfaceTimestamps.slice(-1)[0]]; - }) - .flat() - .filter((timestamp) => !isNaN(timestamp)); - - const minTime = Math.min(...flatTimes); - const maxTime = Math.max(...flatTimes); - - const normalizeTime = (time) => (time - minTime) / (maxTime - minTime); - const normalizeTimePct = (time) => `${normalizeTime(time) * 100}%`; - - for (let name in results) { - const container = document.createElement("div"); - const label = document.createElement("label"); - label.innerText = name; - container.append(label); - - const data = results[name]; - - const barContainer = document.createElement("div"); - Object.assign(barContainer.style, { - height: "10px", - width: "100%", - marginTop: "5px", - border: "1px solid lightgray", - position: "relative", - }); - - if (data.length) { - const firstTime = data[0]; - const lastTime = data[data.length - 1]; - - label.innerText += ` (${firstTime.toFixed(2)} - ${lastTime.toFixed(2)} sec)`; - - const firstTimePct = normalizeTimePct(firstTime); - const lastTimePct = normalizeTimePct(lastTime); - - const width = `calc(${lastTimePct} - ${firstTimePct})`; - - const bar = document.createElement("div"); - - Object.assign(bar.style, { - position: "absolute", - - left: firstTimePct, - width: width, - height: "100%", - background: "blue", - }); - - barContainer.append(bar); - } else { - barContainer.style.background = - "repeating-linear-gradient(45deg, lightgray, lightgray 10px, white 10px, white 20px)"; - } - - container.append(barContainer); - - content.append(container); - } - - modal.open = true; - }, - }, - ], - // onAdded: (path) => { - - // let details = this.getDetails(path) - - // const info = this.addSession(details) - - // const form = this.createForm({ - // ...details, - // info - // }) - - // this.forms.push(form) - - // return { - // key: `sub-${details.subject}/ses-${details.session}`, - // value: form.form - // } - // }, - // onRemoved: (_, path) => { - // let details = this.getDetails(path) - // this.removeSession(details) - // } - }); - - return this.manager; - } -} - -customElements.get("nwbguide-guided-sourcedata-page") || - customElements.define("nwbguide-guided-sourcedata-page", GuidedSourceDataPage); +import Swal from "sweetalert2"; +import { isStorybook } from "../../../../dependencies/globals"; +import { JSONSchemaForm } from "../../../JSONSchemaForm.js"; +import { InstanceManager } from "../../../InstanceManager.js"; +import { ManagedPage } from "./ManagedPage.js"; +import { onThrow } from "../../../../errors"; +import { merge, sanitize } from "../../utils"; +import preprocessSourceDataSchema from "../../../../../../../schemas/source-data.schema"; + +import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; +import { header } from "../../../forms/utils"; +import { Button } from "../../../Button.js"; + +import globalIcon from "../../../../../assets/icons/global.svg?raw"; + +import { baseUrl } from "../../../../server/globals"; + +import { run } from "../options/utils.js"; +import { getInfoFromId } from "./utils"; +import { Modal } from "../../../Modal"; + +const propsToIgnore = { + "*": { + verbose: true, + es_key: true, + exclude_shanks: true, + load_sync_channel: true, + stream_id: true, // NOTE: May be desired for other interfaces + nsx_override: true, + combined: true, + plane_no: true, + }, +}; + +export class GuidedSourceDataPage extends ManagedPage { + constructor(...args) { + super(...args); + this.style.height = "100%"; // Fix main section + } + + beforeSave = () => { + merge(this.localState, this.info.globalState); + }; + + #globalButton = new Button({ + icon: globalIcon, + label: "Edit Default Values", + onClick: () => { + this.#globalModal.form.results = structuredClone(this.info.globalState.project.SourceData ?? {}); + this.#globalModal.open = true; + }, + }); + + header = { + controls: [this.#globalButton], + subtitle: + "Specify the file and folder locations on your local system for each interface, as well as any additional details that might be required.", + }; + + workflow = { + multiple_sessions: { + elements: [this.#globalButton], + }, + }; + + footer = { + onNext: async () => { + await this.save(); // Save in case the conversion fails + + for (let { form } of this.forms) await form.validate(); // Will throw an error in the callback + + // const previousResults = this.info.globalState.metadata.results + + let stillFireSwal = true; + const fireSwal = () => { + Swal.fire({ + title: "Getting metadata for source data", + html: "Please wait...", + allowEscapeKey: false, + allowOutsideClick: false, + heightAuto: false, + backdrop: "rgba(0,0,0, 0.4)", + timerProgressBar: false, + didOpen: () => { + Swal.showLoading(); + }, + }); + }; + + setTimeout(() => { + if (stillFireSwal) fireSwal(); + }); + + await Promise.all( + Object.values(this.forms).map(async ({ subject, session, form }) => { + const info = this.info.globalState.results[subject][session]; + + // NOTE: This clears all user-defined results + const result = await fetch(`${baseUrl}/neuroconv/metadata`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + source_data: sanitize(structuredClone(form.resolved)), // Use resolved values, including global source data + interfaces: this.info.globalState.interfaces, + }), + }) + .then((res) => res.json()) + .catch((error) => { + Swal.close(); + stillFireSwal = false; + this.notify(`Critical Error: ${error.message}`, "error", 4000); + throw error; + }); + + Swal.close(); + + if (isStorybook) return; + + if (result.message) { + const [type, ...splitText] = result.message.split(":"); + const text = splitText.length + ? splitText.join(":").replaceAll("<", "<").replaceAll(">", ">") + : result.traceback + ? `
${result.traceback.trim().split("\n").slice(-2)[0].trim()}
` + : ""; + + const message = `

Request Failed

${type}

${text}

`; + this.notify(message, "error"); + throw result; + } + + const { results: metadata, schema } = result; + + // Merge arrays from generated pipeline data + if (info.metadata.__generated) { + const generated = info.metadata.__generated; + info.metadata = merge(merge(generated, metadata, { arrays: true }), info.metadata); + } + + // Merge new results with old metadata + else merge(metadata, info.metadata); + + // Mirror structure with metadata schema + const schemaGlobal = this.info.globalState.schema; + if (!schemaGlobal.metadata) schemaGlobal.metadata = {}; + if (!schemaGlobal.metadata[subject]) schemaGlobal.metadata[subject] = {}; + schemaGlobal.metadata[subject][session] = schema; + }) + ); + + await this.save(undefined, false); // Just save new raw values + + return this.to(1); + }, + }; + + createForm = ({ subject, session, info }) => { + const hasMultipleSessions = this.workflow.multiple_sessions.value; + + const instanceId = `sub-${subject}/ses-${session}`; + + const schema = structuredClone(this.info.globalState.schema.source_data); + delete schema.description; + + const form = new JSONSchemaForm({ + identifier: instanceId, + schema: preprocessSourceDataSchema(schema), + results: info.source_data, + emptyMessage: "No source data required for this session.", + ignore: propsToIgnore, + globals: hasMultipleSessions ? this.info.globalState.project.SourceData : undefined, + onOverride: (name) => { + this.notify(`${header(name)} has been overridden with a global value.`, "warning", 3000); + }, + // onlyRequired: true, + onUpdate: () => (this.unsavedUpdates = "conversions"), + onStatusChange: (state) => this.manager.updateState(instanceId, state), + onThrow, + }); + + form.style.height = "100%"; + + return { + subject, + session, + form, + }; + }; + + #globalModal = null; + + connectedCallback() { + super.connectedCallback(); + + const schema = structuredClone(this.info.globalState.schema.source_data); + delete schema.description; + + const modal = (this.#globalModal = createGlobalFormModal.call(this, { + header: "Global Source Data", + propsToRemove: { + "*": { + ...propsToIgnore["*"], + folder_path: true, + file_path: true, + // NOTE: Still keeping plural path specifications for now + }, + }, + key: "SourceData", + schema, + hasInstances: true, + })); + document.body.append(modal); + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this.#globalModal) this.#globalModal.remove(); + } + + updated() { + const dashboard = document.querySelector("nwb-dashboard"); + const page = dashboard.page; + } + + render() { + this.localState = { results: structuredClone(this.info.globalState.results ?? {}) }; + + this.forms = this.mapSessions(this.createForm, this.localState.results); + + let instances = {}; + this.forms.forEach(({ subject, session, form }) => { + if (!instances[`sub-${subject}`]) instances[`sub-${subject}`] = {}; + instances[`sub-${subject}`][`ses-${session}`] = form; + }); + + this.manager = new InstanceManager({ + header: "Sessions", + // instanceType: 'Session', + instances, + controls: [ + { + name: "Check Alignment", + primary: true, + onClick: async (id) => { + const { globalState } = this.info; + + const { subject, session } = getInfoFromId(id); + + const souceCopy = structuredClone(globalState.results[subject][session].source_data); + + const sessionInfo = { + interfaces: globalState.interfaces, + source_data: merge(globalState.project.SourceData, souceCopy), + }; + + const results = await run("alignment", sessionInfo, { + title: "Checking Alignment", + message: "Please wait...", + }); + + const header = document.createElement("div"); + const h2 = document.createElement("h2"); + Object.assign(h2.style, { + marginBottom: "10px", + }); + h2.innerText = `Alignment Preview: ${subject}/${session}`; + const warning = document.createElement("small"); + warning.innerHTML = + "Warning: This is just a preview. We do not currently have the features implemented to change the alignment of your interfaces."; + header.append(h2, warning); + + const modal = new Modal({ + header, + }); + + document.body.append(modal); + + const content = document.createElement("div"); + Object.assign(content.style, { + display: "flex", + flexDirection: "column", + gap: "20px", + padding: "20px", + }); + + modal.append(content); + + const flatTimes = Object.values(results) + .map((interfaceTimestamps) => { + return [interfaceTimestamps[0], interfaceTimestamps.slice(-1)[0]]; + }) + .flat() + .filter((timestamp) => !isNaN(timestamp)); + + const minTime = Math.min(...flatTimes); + const maxTime = Math.max(...flatTimes); + + const normalizeTime = (time) => (time - minTime) / (maxTime - minTime); + const normalizeTimePct = (time) => `${normalizeTime(time) * 100}%`; + + for (let name in results) { + const container = document.createElement("div"); + const label = document.createElement("label"); + label.innerText = name; + container.append(label); + + const data = results[name]; + + const barContainer = document.createElement("div"); + Object.assign(barContainer.style, { + height: "10px", + width: "100%", + marginTop: "5px", + border: "1px solid lightgray", + position: "relative", + }); + + if (data.length) { + const firstTime = data[0]; + const lastTime = data[data.length - 1]; + + label.innerText += ` (${firstTime.toFixed(2)} - ${lastTime.toFixed(2)} sec)`; + + const firstTimePct = normalizeTimePct(firstTime); + const lastTimePct = normalizeTimePct(lastTime); + + const width = `calc(${lastTimePct} - ${firstTimePct})`; + + const bar = document.createElement("div"); + + Object.assign(bar.style, { + position: "absolute", + + left: firstTimePct, + width: width, + height: "100%", + background: "blue", + }); + + barContainer.append(bar); + } else { + barContainer.style.background = + "repeating-linear-gradient(45deg, lightgray, lightgray 10px, white 10px, white 20px)"; + } + + container.append(barContainer); + + content.append(container); + } + + modal.open = true; + }, + }, + ], + // onAdded: (path) => { + + // let details = this.getDetails(path) + + // const info = this.addSession(details) + + // const form = this.createForm({ + // ...details, + // info + // }) + + // this.forms.push(form) + + // return { + // key: `sub-${details.subject}/ses-${details.session}`, + // value: form.form + // } + // }, + // onRemoved: (_, path) => { + // let details = this.getDetails(path) + // this.removeSession(details) + // } + }); + + return this.manager; + } +} + +customElements.get("nwbguide-guided-sourcedata-page") || + customElements.define("nwbguide-guided-sourcedata-page", GuidedSourceDataPage); diff --git a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js index b9524020a..ae262f389 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedInspectorPage.js @@ -1,267 +1,267 @@ -import { html } from "lit"; -import { Page } from "../../Page.js"; - -import { unsafeSVG } from "lit/directives/unsafe-svg.js"; -import folderOpenSVG from "../../../../../assets/icons/folder_open.svg?raw"; - -import { electron } from "../../../../electron/index.js"; -import { getSharedPath, removeFilePaths, truncateFilePaths } from "../../../preview/NWBFilePreview.js"; -const { ipcRenderer } = electron; -import { until } from "lit/directives/until.js"; -import { run } from "./utils"; -import { InspectorList, InspectorLegend } from "../../../preview/inspector/InspectorList.js"; -import { getStubArray } from "./GuidedStubPreview.js"; -import { InstanceManager } from "../../../InstanceManager.js"; -import { getMessageType } from "../../../../validation/index.js"; - -import { Button } from "../../../Button.js"; - -import { download } from "../../inspect/utils.js"; -import { createProgressPopup } from "../../../utils/progress.js"; -import { resolve } from "../../../../promises"; - -const filter = (list, toFilter) => { - return list.filter((item) => { - return Object.entries(toFilter) - .map(([key, strOrArray]) => { - return Array.isArray(strOrArray) - ? strOrArray.map((str) => item[key].includes(str)) - : item[key].includes(strOrArray); - }) - .flat() - .every(Boolean); - }); -}; - -const emptyMessage = "No issues detected in these files!"; - -export class GuidedInspectorPage extends Page { - constructor(...args) { - super(...args); - this.style.height = "100%"; // Fix main section - - Object.assign(this.style, { - display: "grid", - gridTemplateRows: "calc(100% - 120px) 1fr", - rowGap: "10px", - }); - } - - workflow = { - multiple_sessions: {}, - }; - - headerButtons = [ - new Button({ - label: "JSON", - primary: true, - }), - - new Button({ - label: "Text", - primary: true, - }), - ]; - - header = { - subtitle: `The NWB Inspector has scanned your files for adherence to best practices.`, - controls: () => [ - ...this.headerButtons, - html` { - if (ipcRenderer) - ipcRenderer.send( - "showItemInFolder", - getSharedPath(getStubArray(this.info.globalState.preview.stubs).map(({ file }) => file)) - ); - }} - >${unsafeSVG(folderOpenSVG)}`, - ], - }; - - // NOTE: We may want to trigger this whenever (1) this page is visited AND (2) data has been changed. - footer = {}; - - #toggleRendered; - #rendered; - #updateRendered = (force) => - force || this.#rendered === true - ? (this.#rendered = new Promise( - (resolve) => (this.#toggleRendered = () => resolve((this.#rendered = true))) - )) - : this.#rendered; - - get rendered() { - return resolve(this.#rendered, () => true); - } - - getStatus = (list) => { - return list.reduce((acc, messageInfo) => { - const res = getMessageType(messageInfo); - if (acc === "error") return acc; - else return res; - }, "valid"); - }; - - updated() { - const [downloadJSONButton, downloadTextButton] = this.headerButtons; - - downloadJSONButton.onClick = () => - download("nwb-inspector-report.json", { - header: this.report.header, - messages: this.report.messages, - }); - - downloadTextButton.onClick = () => download("nwb-inspector-report.txt", this.report.text); - } - - render() { - this.#updateRendered(true); - - const { globalState } = this.info; - const { stubs, inspector } = globalState.preview; - - const legendProps = { multiple: this.workflow.multiple_sessions.value }; - - const options = {}; // NOTE: Currently options are handled on the Python end until exposed to the user - const title = "Inspecting your file"; - - const fileArr = Object.entries(stubs) - .map(([subject, v]) => - Object.entries(v).map(([session, info]) => { - return { subject, session, info }; - }) - ) - .flat(); - return html` - ${until( - (async () => { - if (fileArr.length <= 1) { - this.report = inspector; - - if (!this.report) { - const result = await run( - "inspect_file", - { nwbfile_path: fileArr[0].info.file, ...options }, - { title } - ).catch((error) => { - this.notify(error.message, "error"); - return null; - }); - - if (!result) return "Failed to generate inspector report."; - - this.report = globalState.preview.inspector = { - ...result, - messages: removeFilePaths(result.messages), - }; - } - - if (!inspector) await this.save(); - - const items = this.report.messages; - - const list = new InspectorList({ items, emptyMessage }); - - Object.assign(list.style, { - height: "100%", - }); - - return html`${list}${new InspectorLegend(legendProps)}`; - } - - const path = getSharedPath(fileArr.map(({ info }) => info.file)); - - this.report = inspector; - if (!this.report) { - const swalOpts = await createProgressPopup({ title: `${title}s` }); - - const { close: closeProgressPopup } = swalOpts; - - const result = await run( - "inspect_folder", - { path, ...options, request_id: swalOpts.id }, - swalOpts - ).catch((error) => { - this.notify(error.message, "error"); - closeProgressPopup(); - return null; - }); - - if (!result) return "Failed to generate inspector report."; - - closeProgressPopup(); - - this.report = globalState.preview.inspector = { - ...result, - messages: truncateFilePaths(result.messages, path), - }; - } - - if (!inspector) await this.save(); - - const messages = this.report.messages; - const items = truncateFilePaths(messages, path); - - const _instances = fileArr.map(({ subject, session, info }) => { - const file_path = [`sub-${subject}`, `sub-${subject}_ses-${session}`]; - const filtered = removeFilePaths(filter(items, { file_path })); - - const display = () => new InspectorList({ items: filtered, emptyMessage }); - display.status = this.getStatus(filtered); - - return { - subject, - session, - display, - }; - }); - - const instances = _instances.reduce((acc, { subject, session, display }) => { - const subLabel = `sub-${subject}`; - if (!acc[`sub-${subject}`]) acc[subLabel] = {}; - acc[subLabel][`ses-${session}`] = display; - return acc; - }, {}); - - Object.keys(instances).forEach((subLabel) => { - // const subItems = filter(items, { file_path: `${subLabel}${nodePath.sep}${subLabel}_ses-` }); // NOTE: This will not run on web-only now - const subItems = filter(items, { file_path: `${subLabel}_ses-` }); // NOTE: This will not run on web-only now - const path = getSharedPath(subItems.map((item) => item.file_path)); - const filtered = truncateFilePaths(subItems, path); - - const display = () => new InspectorList({ items: filtered, emptyMessage }); - display.status = this.getStatus(filtered); - - instances[subLabel] = { - ["All Files"]: display, - ...instances[subLabel], - }; - }); - - const allDisplay = () => new InspectorList({ items, emptyMessage }); - allDisplay.status = this.getStatus(items); - - const allInstances = { - ["All Files"]: allDisplay, - ...instances, - }; - - const manager = new InstanceManager({ - instances: allInstances, - }); - - return html`${manager}${new InspectorLegend(legendProps)}`; - })().finally(() => { - this.#toggleRendered(); - }), - "Loading inspector report..." - )} - `; - } -} - -customElements.get("nwbguide-guided-inspector-page") || - customElements.define("nwbguide-guided-inspector-page", GuidedInspectorPage); +import { html } from "lit"; +import { Page } from "../../Page.js"; + +import { unsafeSVG } from "lit/directives/unsafe-svg.js"; +import folderOpenSVG from "../../../../../assets/icons/folder_open.svg?raw"; + +import { electron } from "../../../../electron/index.js"; +import { getSharedPath, removeFilePaths, truncateFilePaths } from "../../../preview/NWBFilePreview.js"; +const { ipcRenderer } = electron; +import { until } from "lit/directives/until.js"; +import { run } from "./utils"; +import { InspectorList, InspectorLegend } from "../../../preview/inspector/InspectorList.js"; +import { getStubArray } from "./GuidedStubPreview.js"; +import { InstanceManager } from "../../../InstanceManager.js"; +import { getMessageType } from "../../../../validation/index.js"; + +import { Button } from "../../../Button.js"; + +import { download } from "../../inspect/utils.js"; +import { createProgressPopup } from "../../../utils/progress.js"; +import { resolve } from "../../../../promises"; + +const filter = (list, toFilter) => { + return list.filter((item) => { + return Object.entries(toFilter) + .map(([key, strOrArray]) => { + return Array.isArray(strOrArray) + ? strOrArray.map((str) => item[key].includes(str)) + : item[key].includes(strOrArray); + }) + .flat() + .every(Boolean); + }); +}; + +const emptyMessage = "No issues detected in these files!"; + +export class GuidedInspectorPage extends Page { + constructor(...args) { + super(...args); + this.style.height = "100%"; // Fix main section + + Object.assign(this.style, { + display: "grid", + gridTemplateRows: "calc(100% - 120px) 1fr", + rowGap: "10px", + }); + } + + workflow = { + multiple_sessions: {}, + }; + + headerButtons = [ + new Button({ + label: "JSON", + primary: true, + }), + + new Button({ + label: "Text", + primary: true, + }), + ]; + + header = { + subtitle: `The NWB Inspector has scanned your files for adherence to best practices.`, + controls: () => [ + ...this.headerButtons, + html` { + if (ipcRenderer) + ipcRenderer.send( + "showItemInFolder", + getSharedPath(getStubArray(this.info.globalState.preview.stubs).map(({ file }) => file)) + ); + }} + >${unsafeSVG(folderOpenSVG)}`, + ], + }; + + // NOTE: We may want to trigger this whenever (1) this page is visited AND (2) data has been changed. + footer = {}; + + #toggleRendered; + #rendered; + #updateRendered = (force) => + force || this.#rendered === true + ? (this.#rendered = new Promise( + (resolve) => (this.#toggleRendered = () => resolve((this.#rendered = true))) + )) + : this.#rendered; + + get rendered() { + return resolve(this.#rendered, () => true); + } + + getStatus = (list) => { + return list.reduce((acc, messageInfo) => { + const res = getMessageType(messageInfo); + if (acc === "error") return acc; + else return res; + }, "valid"); + }; + + updated() { + const [downloadJSONButton, downloadTextButton] = this.headerButtons; + + downloadJSONButton.onClick = () => + download("nwb-inspector-report.json", { + header: this.report.header, + messages: this.report.messages, + }); + + downloadTextButton.onClick = () => download("nwb-inspector-report.txt", this.report.text); + } + + render() { + this.#updateRendered(true); + + const { globalState } = this.info; + const { stubs, inspector } = globalState.preview; + + const legendProps = { multiple: this.workflow.multiple_sessions.value }; + + const options = {}; // NOTE: Currently options are handled on the Python end until exposed to the user + const title = "Inspecting your file"; + + const fileArr = Object.entries(stubs) + .map(([subject, v]) => + Object.entries(v).map(([session, info]) => { + return { subject, session, info }; + }) + ) + .flat(); + return html` + ${until( + (async () => { + if (fileArr.length <= 1) { + this.report = inspector; + + if (!this.report) { + const result = await run( + "inspect_file", + { nwbfile_path: fileArr[0].info.file, ...options }, + { title } + ).catch((error) => { + this.notify(error.message, "error"); + return null; + }); + + if (!result) return "Failed to generate inspector report."; + + this.report = globalState.preview.inspector = { + ...result, + messages: removeFilePaths(result.messages), + }; + } + + if (!inspector) await this.save(); + + const items = this.report.messages; + + const list = new InspectorList({ items, emptyMessage }); + + Object.assign(list.style, { + height: "100%", + }); + + return html`${list}${new InspectorLegend(legendProps)}`; + } + + const path = getSharedPath(fileArr.map(({ info }) => info.file)); + + this.report = inspector; + if (!this.report) { + const swalOpts = await createProgressPopup({ title: `${title}s` }); + + const { close: closeProgressPopup } = swalOpts; + + const result = await run( + "inspect_folder", + { path, ...options, request_id: swalOpts.id }, + swalOpts + ).catch((error) => { + this.notify(error.message, "error"); + closeProgressPopup(); + return null; + }); + + if (!result) return "Failed to generate inspector report."; + + closeProgressPopup(); + + this.report = globalState.preview.inspector = { + ...result, + messages: truncateFilePaths(result.messages, path), + }; + } + + if (!inspector) await this.save(); + + const messages = this.report.messages; + const items = truncateFilePaths(messages, path); + + const _instances = fileArr.map(({ subject, session, info }) => { + const file_path = [`sub-${subject}`, `sub-${subject}_ses-${session}`]; + const filtered = removeFilePaths(filter(items, { file_path })); + + const display = () => new InspectorList({ items: filtered, emptyMessage }); + display.status = this.getStatus(filtered); + + return { + subject, + session, + display, + }; + }); + + const instances = _instances.reduce((acc, { subject, session, display }) => { + const subLabel = `sub-${subject}`; + if (!acc[`sub-${subject}`]) acc[subLabel] = {}; + acc[subLabel][`ses-${session}`] = display; + return acc; + }, {}); + + Object.keys(instances).forEach((subLabel) => { + // const subItems = filter(items, { file_path: `${subLabel}${nodePath.sep}${subLabel}_ses-` }); // NOTE: This will not run on web-only now + const subItems = filter(items, { file_path: `${subLabel}_ses-` }); // NOTE: This will not run on web-only now + const path = getSharedPath(subItems.map((item) => item.file_path)); + const filtered = truncateFilePaths(subItems, path); + + const display = () => new InspectorList({ items: filtered, emptyMessage }); + display.status = this.getStatus(filtered); + + instances[subLabel] = { + ["All Files"]: display, + ...instances[subLabel], + }; + }); + + const allDisplay = () => new InspectorList({ items, emptyMessage }); + allDisplay.status = this.getStatus(items); + + const allInstances = { + ["All Files"]: allDisplay, + ...instances, + }; + + const manager = new InstanceManager({ + instances: allInstances, + }); + + return html`${manager}${new InspectorLegend(legendProps)}`; + })().finally(() => { + this.#toggleRendered(); + }), + "Loading inspector report..." + )} + `; + } +} + +customElements.get("nwbguide-guided-inspector-page") || + customElements.define("nwbguide-guided-inspector-page", GuidedInspectorPage); diff --git a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedStubPreview.js b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedStubPreview.js index 9ce737ab1..d1b022155 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedStubPreview.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedStubPreview.js @@ -1,57 +1,57 @@ -import { html } from "lit"; -import { Page } from "../../Page.js"; - -import { unsafeSVG } from "lit/directives/unsafe-svg.js"; -import folderOpenSVG from "../../../../../assets/icons/folder_open.svg?raw"; - -import { electron } from "../../../../electron/index.js"; -import { NWBFilePreview, getSharedPath } from "../../../preview/NWBFilePreview.js"; -const { ipcRenderer } = electron; - -export const getStubArray = (stubs) => - Object.values(stubs) - .map((item) => Object.values(item)) - .flat(); - -export class GuidedStubPreviewPage extends Page { - constructor(...args) { - super(...args); - this.style.height = "100%"; // Fix main section - } - - header = { - subtitle: `Preview file contents on truncated files using the Neurosift application`, - controls: () => - html` { - if (ipcRenderer) - ipcRenderer.send( - "showItemInFolder", - getSharedPath(getStubArray(this.info.globalState.preview.stubs).map((item) => item.file)) - ); - }} - >${unsafeSVG(folderOpenSVG)}`, - }; - - // NOTE: We may want to trigger this whenever (1) this page is visited AND (2) data has been changed. - footer = { - next: "Run Conversion", - onNext: async () => { - await this.save(); // Save in case the conversion fails - return this.to(1); // Will trigger conversion if necessary - }, - }; - - render() { - const { preview, project } = this.info.globalState; - - return preview.stubs - ? new NWBFilePreview({ project: project.name, files: preview.stubs }) - : html`

Your conversion preview failed. Please try again.

`; - } -} - -customElements.get("nwbguide-guided-stub-review-page") || - customElements.define("nwbguide-guided-stub-review-page", GuidedStubPreviewPage); +import { html } from "lit"; +import { Page } from "../../Page.js"; + +import { unsafeSVG } from "lit/directives/unsafe-svg.js"; +import folderOpenSVG from "../../../../../assets/icons/folder_open.svg?raw"; + +import { electron } from "../../../../electron/index.js"; +import { NWBFilePreview, getSharedPath } from "../../../preview/NWBFilePreview.js"; +const { ipcRenderer } = electron; + +export const getStubArray = (stubs) => + Object.values(stubs) + .map((item) => Object.values(item)) + .flat(); + +export class GuidedStubPreviewPage extends Page { + constructor(...args) { + super(...args); + this.style.height = "100%"; // Fix main section + } + + header = { + subtitle: `Preview file contents on truncated files using the Neurosift application`, + controls: () => + html` { + if (ipcRenderer) + ipcRenderer.send( + "showItemInFolder", + getSharedPath(getStubArray(this.info.globalState.preview.stubs).map((item) => item.file)) + ); + }} + >${unsafeSVG(folderOpenSVG)}`, + }; + + // NOTE: We may want to trigger this whenever (1) this page is visited AND (2) data has been changed. + footer = { + next: "Run Conversion", + onNext: async () => { + await this.save(); // Save in case the conversion fails + return this.to(1); // Will trigger conversion if necessary + }, + }; + + render() { + const { preview, project } = this.info.globalState; + + return preview.stubs + ? new NWBFilePreview({ project: project.name, files: preview.stubs }) + : html`

Your conversion preview failed. Please try again.

`; + } +} + +customElements.get("nwbguide-guided-stub-review-page") || + customElements.define("nwbguide-guided-stub-review-page", GuidedStubPreviewPage); diff --git a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js index 92910b69f..075b1be03 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/options/GuidedUpload.js @@ -1,204 +1,204 @@ -import { html } from "lit"; -import { JSONSchemaForm } from "../../../JSONSchemaForm.js"; -import { Page } from "../../Page.js"; -import { onThrow } from "../../../../errors"; -import { merge } from "../../utils"; -import Swal from "sweetalert2"; -import dandiUploadSchema, { ready, regenerateDandisets } from "../../../../../../../schemas/dandi-upload.schema"; -import { createDandiset, uploadToDandi } from "../../uploads/UploadsPage.js"; -import { until } from "lit/directives/until.js"; - -import { Button } from "../../../Button.js"; - -import keyIcon from "../../../../../assets/icons/key.svg?raw"; - -import { validate } from "../../uploads/utils"; -import { global } from "../../../../progress/index.js"; - -import dandiGlobalSchema from "../../../../../../../schemas/json/dandi/global.json"; -import { createFormModal } from "../../../forms/GlobalFormModal"; -import { validateDANDIApiKey } from "../../../../validation/dandi"; -import { resolve } from "../../../../promises"; - -export class GuidedUploadPage extends Page { - constructor(...args) { - super(...args); - } - - form; - - beforeSave = () => { - const globalState = this.info.globalState; - const isNewDandiset = globalState.upload?.dandiset !== this.localState.dandiset; - merge({ upload: this.localState }, globalState); // Merge the local and global states - if (isNewDandiset) delete globalState.upload.results; // Clear the preview results entirely if a new Dandiset - }; - - header = { - subtitle: "Configure your upload to the DANDI Archive", - controls: [ - new Button({ - icon: keyIcon, - label: "API Keys", - onClick: () => { - this.globalModal.form.results = structuredClone(global.data.DANDI?.api_keys ?? {}); - this.globalModal.open = true; - }, - }), - ], - }; - - workflow = { - upload_to_dandi: { - condition: (v) => v === false, - skip: true, - }, - }; - - globalModal = null; - #saveNotification; - - connectedCallback() { - super.connectedCallback(); - - const modal = (this.globalModal = createFormModal.call(this, { - header: "DANDI API Keys", - schema: dandiGlobalSchema.properties.api_keys, - onSave: async (form) => { - const apiKeys = form.resolved; - - if (this.#saveNotification) this.dismiss(this.#saveNotification); - - if (!Object.keys(apiKeys).length) { - this.#saveNotification = this.notify("No API keys were provided", "error"); - return null; - } - - // Ensure values exist - const globalDandiData = global.data.DANDI ?? (global.data.DANDI = {}); - if (!globalDandiData.api_keys) globalDandiData.api_keys = {}; - merge(apiKeys, globalDandiData.api_keys); - - global.save(); - await regenerateDandisets(); - const input = this.form.getFormElement(["dandiset"]); - input.requestUpdate(); - }, - formProps: { - validateOnChange: async (name, parent) => { - const value = parent[name]; - if (name.includes("api_key")) return await validateDANDIApiKey(value, name.includes("staging")); - }, - }, - })); - document.body.append(modal); - } - - disconnectedCallback() { - super.disconnectedCallback(); - if (this.globalModal) this.globalModal.remove(); - } - - footer = { - next: "Upload", - onNext: async () => { - await this.save(); // Save in case the conversion fails - - const globalState = this.info.globalState; - const globalUploadInfo = globalState.upload; - - await this.form.validate(); // Will throw an error in the callback - - // Catch if Dandiset is already uploaded - if ("results" in globalUploadInfo) { - const result = await Swal.fire({ - title: "This pipeline has already uploaded to DANDI", - html: "Would you like to reupload the latest files?", - icon: "warning", - showCancelButton: true, - confirmButtonColor: "#3085d6", - confirmButtonText: "Continue with Reupload", - cancelButtonText: "Skip Upload", - }); - - if (!result || !result.isConfirmed) return this.to(1); - } - - globalUploadInfo.results = await uploadToDandi.call(this, { - ...globalUploadInfo.info, - project: globalState.project.name, - }); - - this.unsavedUpdates = true; - - return this.to(1); - }, - }; - - #toggleRendered; - #rendered; - #updateRendered = (force) => - force || this.#rendered === true - ? (this.#rendered = new Promise( - (resolve) => (this.#toggleRendered = () => resolve((this.#rendered = true))) - )) - : this.#rendered; - - get rendered() { - return resolve(this.#rendered, () => true); - } - - async updated() { - await this.rendered; - } - - render() { - this.#updateRendered(true); - - const state = (this.localState = structuredClone(this.info.globalState.upload ?? { info: {} })); - - const promise = ready.cpus - .then(() => ready.dandisets) - .then(() => { - return (this.form = new JSONSchemaForm({ - schema: dandiUploadSchema, - results: state.info, - controls: { - dandiset: [ - new Button({ - label: "Create New Dandiset", - buttonStyles: { - width: "max-content", - }, - onClick: async () => { - await createDandiset.call(this, { title: this.form.resolved.dandiset }); - this.requestUpdate(); - }, - }), - ], - }, - onUpdate: () => (this.unsavedUpdates = true), - onThrow, - validateOnChange: validate, - })); - }) - .catch((error) => html`

${error}

`); - - // Confirm that one api key exists - promise.then(() => { - const api_keys = global.data.DANDI?.api_keys; - if (!api_keys || !Object.keys(api_keys).length) this.globalModal.open = true; - }); - - const untilResult = until(promise, html`Loading form contents...`); - - promise.then(() => { - this.#toggleRendered(); - }); - - return untilResult; - } -} - -customElements.get("nwbguide-guided-upload-page") || - customElements.define("nwbguide-guided-upload-page", GuidedUploadPage); +import { html } from "lit"; +import { JSONSchemaForm } from "../../../JSONSchemaForm.js"; +import { Page } from "../../Page.js"; +import { onThrow } from "../../../../errors"; +import { merge } from "../../utils"; +import Swal from "sweetalert2"; +import dandiUploadSchema, { ready, regenerateDandisets } from "../../../../../../../schemas/dandi-upload.schema"; +import { createDandiset, uploadToDandi } from "../../uploads/UploadsPage.js"; +import { until } from "lit/directives/until.js"; + +import { Button } from "../../../Button.js"; + +import keyIcon from "../../../../../assets/icons/key.svg?raw"; + +import { validate } from "../../uploads/utils"; +import { global } from "../../../../progress/index.js"; + +import dandiGlobalSchema from "../../../../../../../schemas/json/dandi/global.json"; +import { createFormModal } from "../../../forms/GlobalFormModal"; +import { validateDANDIApiKey } from "../../../../validation/dandi"; +import { resolve } from "../../../../promises"; + +export class GuidedUploadPage extends Page { + constructor(...args) { + super(...args); + } + + form; + + beforeSave = () => { + const globalState = this.info.globalState; + const isNewDandiset = globalState.upload?.dandiset !== this.localState.dandiset; + merge({ upload: this.localState }, globalState); // Merge the local and global states + if (isNewDandiset) delete globalState.upload.results; // Clear the preview results entirely if a new Dandiset + }; + + header = { + subtitle: "Configure your upload to the DANDI Archive", + controls: [ + new Button({ + icon: keyIcon, + label: "API Keys", + onClick: () => { + this.globalModal.form.results = structuredClone(global.data.DANDI?.api_keys ?? {}); + this.globalModal.open = true; + }, + }), + ], + }; + + workflow = { + upload_to_dandi: { + condition: (v) => v === false, + skip: true, + }, + }; + + globalModal = null; + #saveNotification; + + connectedCallback() { + super.connectedCallback(); + + const modal = (this.globalModal = createFormModal.call(this, { + header: "DANDI API Keys", + schema: dandiGlobalSchema.properties.api_keys, + onSave: async (form) => { + const apiKeys = form.resolved; + + if (this.#saveNotification) this.dismiss(this.#saveNotification); + + if (!Object.keys(apiKeys).length) { + this.#saveNotification = this.notify("No API keys were provided", "error"); + return null; + } + + // Ensure values exist + const globalDandiData = global.data.DANDI ?? (global.data.DANDI = {}); + if (!globalDandiData.api_keys) globalDandiData.api_keys = {}; + merge(apiKeys, globalDandiData.api_keys); + + global.save(); + await regenerateDandisets(); + const input = this.form.getFormElement(["dandiset"]); + input.requestUpdate(); + }, + formProps: { + validateOnChange: async (name, parent) => { + const value = parent[name]; + if (name.includes("api_key")) return await validateDANDIApiKey(value, name.includes("staging")); + }, + }, + })); + document.body.append(modal); + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this.globalModal) this.globalModal.remove(); + } + + footer = { + next: "Upload", + onNext: async () => { + await this.save(); // Save in case the conversion fails + + const globalState = this.info.globalState; + const globalUploadInfo = globalState.upload; + + await this.form.validate(); // Will throw an error in the callback + + // Catch if Dandiset is already uploaded + if ("results" in globalUploadInfo) { + const result = await Swal.fire({ + title: "This pipeline has already uploaded to DANDI", + html: "Would you like to reupload the latest files?", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + confirmButtonText: "Continue with Reupload", + cancelButtonText: "Skip Upload", + }); + + if (!result || !result.isConfirmed) return this.to(1); + } + + globalUploadInfo.results = await uploadToDandi.call(this, { + ...globalUploadInfo.info, + project: globalState.project.name, + }); + + this.unsavedUpdates = true; + + return this.to(1); + }, + }; + + #toggleRendered; + #rendered; + #updateRendered = (force) => + force || this.#rendered === true + ? (this.#rendered = new Promise( + (resolve) => (this.#toggleRendered = () => resolve((this.#rendered = true))) + )) + : this.#rendered; + + get rendered() { + return resolve(this.#rendered, () => true); + } + + async updated() { + await this.rendered; + } + + render() { + this.#updateRendered(true); + + const state = (this.localState = structuredClone(this.info.globalState.upload ?? { info: {} })); + + const promise = ready.cpus + .then(() => ready.dandisets) + .then(() => { + return (this.form = new JSONSchemaForm({ + schema: dandiUploadSchema, + results: state.info, + controls: { + dandiset: [ + new Button({ + label: "Create New Dandiset", + buttonStyles: { + width: "max-content", + }, + onClick: async () => { + await createDandiset.call(this, { title: this.form.resolved.dandiset }); + this.requestUpdate(); + }, + }), + ], + }, + onUpdate: () => (this.unsavedUpdates = true), + onThrow, + validateOnChange: validate, + })); + }) + .catch((error) => html`

${error}

`); + + // Confirm that one api key exists + promise.then(() => { + const api_keys = global.data.DANDI?.api_keys; + if (!api_keys || !Object.keys(api_keys).length) this.globalModal.open = true; + }); + + const untilResult = until(promise, html`Loading form contents...`); + + promise.then(() => { + this.#toggleRendered(); + }); + + return untilResult; + } +} + +customElements.get("nwbguide-guided-upload-page") || + customElements.define("nwbguide-guided-upload-page", GuidedUploadPage); diff --git a/src/electron/renderer/src/stories/pages/guided-mode/results/GuidedResults.js b/src/electron/renderer/src/stories/pages/guided-mode/results/GuidedResults.js index a041ac937..1cc68fa86 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/results/GuidedResults.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/results/GuidedResults.js @@ -1,59 +1,59 @@ -import { html } from "lit"; -import { unsafeSVG } from "lit/directives/unsafe-svg.js"; -import folderOpenSVG from "../../../../../assets/icons/folder_open.svg?raw"; - -import { Page } from "../../Page.js"; -import { getStubArray } from "../options/GuidedStubPreview.js"; -import { getSharedPath } from "../../../preview/NWBFilePreview.js"; - -import { electron, path } from "../../../../electron/index.js"; -const { ipcRenderer } = electron; - -export class GuidedResultsPage extends Page { - constructor(...args) { - super(...args); - } - - header = { - controls: () => - html` { - if (ipcRenderer) ipcRenderer.send("showItemInFolder", this.#sharedPath()); - }} - >${unsafeSVG(folderOpenSVG)}`, - }; - - footer = {}; - - #sharedPath = () => { - const { conversion } = this.info.globalState; - if (!conversion) return ""; - return getSharedPath(getStubArray(conversion).map((item) => item.file)); - }; - - updated() { - this.save(); // Save the current state - } - - render() { - const { conversion } = this.info.globalState; - - if (!conversion) - return html`

Your conversion failed. Please try again.

`; - - return html` -

Your data was successfully converted to NWB!

-
    - ${getStubArray(conversion) - .map(({ file }) => file.split(path.sep).slice(-1)[0]) - .sort() - .map((id) => html`
  1. ${id}
  2. `)} -
- `; - } -} - -customElements.get("nwbguide-guided-results-page") || - customElements.define("nwbguide-guided-results-page", GuidedResultsPage); +import { html } from "lit"; +import { unsafeSVG } from "lit/directives/unsafe-svg.js"; +import folderOpenSVG from "../../../../../assets/icons/folder_open.svg?raw"; + +import { Page } from "../../Page.js"; +import { getStubArray } from "../options/GuidedStubPreview.js"; +import { getSharedPath } from "../../../preview/NWBFilePreview.js"; + +import { electron, path } from "../../../../electron/index.js"; +const { ipcRenderer } = electron; + +export class GuidedResultsPage extends Page { + constructor(...args) { + super(...args); + } + + header = { + controls: () => + html` { + if (ipcRenderer) ipcRenderer.send("showItemInFolder", this.#sharedPath()); + }} + >${unsafeSVG(folderOpenSVG)}`, + }; + + footer = {}; + + #sharedPath = () => { + const { conversion } = this.info.globalState; + if (!conversion) return ""; + return getSharedPath(getStubArray(conversion).map((item) => item.file)); + }; + + updated() { + this.save(); // Save the current state + } + + render() { + const { conversion } = this.info.globalState; + + if (!conversion) + return html`

Your conversion failed. Please try again.

`; + + return html` +

Your data was successfully converted to NWB!

+
    + ${getStubArray(conversion) + .map(({ file }) => file.split(path.sep).slice(-1)[0]) + .sort() + .map((id) => html`
  1. ${id}
  2. `)} +
+ `; + } +} + +customElements.get("nwbguide-guided-results-page") || + customElements.define("nwbguide-guided-results-page", GuidedResultsPage); diff --git a/src/electron/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js b/src/electron/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js index aa0aaf32f..7d1d51677 100644 --- a/src/electron/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js +++ b/src/electron/renderer/src/stories/pages/guided-mode/setup/GuidedSubjects.js @@ -1,205 +1,205 @@ -import { html } from "lit"; -import { Page } from "../../Page.js"; -import getSubjectSchema from "../../../../../../../schemas/subject.schema"; -import { validateOnChange } from "../../../../validation/index.js"; -import { Table } from "../../../Table.js"; - -import { updateResultsFromSubjects } from "./utils"; -import { preprocessMetadataSchema } from "../../../../../../../schemas/base-metadata.schema"; -import { Button } from "../../../Button.js"; -import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; -import { header } from "../../../forms/utils"; - -import globalIcon from "../../../../../assets/icons/global.svg?raw"; - -export class GuidedSubjectsPage extends Page { - constructor(...args) { - super(...args); - } - - #addButton = new Button({ - label: "Add Subject", - onClick: () => this.table.table.alter("insert_row_below"), - }); - - #globalButton = new Button({ - icon: globalIcon, - label: "Edit Default Metadata", - onClick: () => { - this.#globalModal.form.results = structuredClone(this.info.globalState.project.Subject ?? {}); - this.#globalModal.open = true; - }, - }); - - workflow = { - multiple_sessions: { - elements: [this.#globalButton, this.#addButton], - }, - }; - - header = { - subtitle: "Enter all metadata known about each experiment subject", - controls: [this.#globalButton], - }; - - workflow = { - multiple_sessions: { - skip: () => {}, - }, - }; - - // Abort save if subject structure is invalid - beforeSave = () => { - try { - this.table.validate(); - } catch (error) { - this.notify(error.message, "error"); - throw error; - } - - const localState = this.table.data; - - // Create map of original names to new names - const nameMap = {}; - for (let key in this.#originalState) { - const renamed = Object.keys(localState).find( - (k) => localState[k].identifier === this.#originalState[key].identifier - ); - nameMap[key] = renamed; - } - - // Remove identifiers - for (let key in localState) delete localState[key].identifier; - - // Local state is the source of truth - this.info.globalState.subjects = localState; - - const { results, subjects } = this.info.globalState; - - const sourceDataObject = Object.keys(this.info.globalState.interfaces).reduce((acc, key) => { - acc[key] = {}; - return acc; - }, {}); - - // Modify the results object to track new subjects / sessions - updateResultsFromSubjects(results, subjects, sourceDataObject, nameMap); // NOTE: This directly mutates the results object - }; - - footer = { - onNext: () => { - const extraElements = document.querySelectorAll(".HandsontableCopyPaste"); - extraElements.forEach((element) => element.remove()); - return this.to(1); - }, - }; - - updated() { - super.updated(); // Call if updating data - } - - #globalModal; - - connectedCallback() { - super.connectedCallback(); - - const schema = preprocessMetadataSchema(undefined, true).properties.Subject; - - const modal = (this.#globalModal = createGlobalFormModal.call(this, { - header: "Global Subject Metadata", - key: "Subject", - validateEmptyValues: null, - schema, - formProps: { - validateOnChange: (localPath, parent, path) => { - return validateOnChange(localPath, parent, ["Subject", ...path]); - }, - }, - })); - document.body.append(modal); - } - - disconnectedCallback() { - super.disconnectedCallback(); - if (this.#globalModal) this.#globalModal.remove(); - } - - #originalState = {}; - - render() { - const hasMultipleSessions = this.workflow.multiple_sessions.value; - - const subjects = (this.localState = structuredClone(this.info.globalState.subjects ?? {})); - - // Ensure all the proper subjects are in the global state - const toHave = Object.keys(this.info.globalState.results); - const toRemove = Object.keys(subjects).filter((sub) => !toHave.includes(sub)); - toRemove.forEach((sub) => delete subjects[sub]); - toHave.forEach((sub) => (subjects[sub] = subjects[sub] ?? {})); - - this.#originalState = structuredClone(subjects); - - for (let subject in subjects) { - const sessions = Object.keys(this.info.globalState.results[subject]); - subjects[subject].sessions = sessions; - subjects[subject].identifier = this.#originalState[subject].identifier = Symbol("subject"); // Add identifier to subject - } - - const contextMenuConfig = { ignore: ["row_below"] }; - - if (!hasMultipleSessions) contextMenuConfig.ignore.push("remove_row"); - - this.table = new Table({ - schema: { - type: "array", - items: getSubjectSchema(), - }, - data: subjects, - globals: hasMultipleSessions ? this.info.globalState.project.Subject : undefined, - keyColumn: "subject_id", - validateEmptyCells: ["subject_id", "sessions"], - contextMenu: contextMenuConfig, - groups: [ - [ - "sex", - "species", - // 'age' - ], // Validate both when one is changed - ], - onThrow: (message, type) => this.notify(message, type), - onOverride: (name) => { - this.notify(`${header(name)} has been overridden with a global value.`, "warning", 3000); - }, - onUpdate: () => { - this.unsavedUpdates = "conversions"; - }, - validateOnChange: function (localPath, parent, v) { - const name = localPath[localPath.length - 1]; - - if (name === "sessions") { - if (v?.length) return true; - else { - return [ - { - message: "Sessions must have at least one entry", - type: "error", - }, - ]; - } - } else { - delete parent.sessions; // Delete sessions from parent copy - return validateOnChange.call(this, name, parent, ["Subject", ...localPath.slice(0, -1)], v); - } - }, - }); - - return html` -
-
${this.table}
- ${this.#addButton} -
- `; - } -} - -customElements.get("nwbguide-guided-subjects-page") || - customElements.define("nwbguide-guided-subjects-page", GuidedSubjectsPage); +import { html } from "lit"; +import { Page } from "../../Page.js"; +import getSubjectSchema from "../../../../../../../schemas/subject.schema"; +import { validateOnChange } from "../../../../validation/index.js"; +import { Table } from "../../../Table.js"; + +import { updateResultsFromSubjects } from "./utils"; +import { preprocessMetadataSchema } from "../../../../../../../schemas/base-metadata.schema"; +import { Button } from "../../../Button.js"; +import { createGlobalFormModal } from "../../../forms/GlobalFormModal"; +import { header } from "../../../forms/utils"; + +import globalIcon from "../../../../../assets/icons/global.svg?raw"; + +export class GuidedSubjectsPage extends Page { + constructor(...args) { + super(...args); + } + + #addButton = new Button({ + label: "Add Subject", + onClick: () => this.table.table.alter("insert_row_below"), + }); + + #globalButton = new Button({ + icon: globalIcon, + label: "Edit Default Metadata", + onClick: () => { + this.#globalModal.form.results = structuredClone(this.info.globalState.project.Subject ?? {}); + this.#globalModal.open = true; + }, + }); + + workflow = { + multiple_sessions: { + elements: [this.#globalButton, this.#addButton], + }, + }; + + header = { + subtitle: "Enter all metadata known about each experiment subject", + controls: [this.#globalButton], + }; + + workflow = { + multiple_sessions: { + skip: () => {}, + }, + }; + + // Abort save if subject structure is invalid + beforeSave = () => { + try { + this.table.validate(); + } catch (error) { + this.notify(error.message, "error"); + throw error; + } + + const localState = this.table.data; + + // Create map of original names to new names + const nameMap = {}; + for (let key in this.#originalState) { + const renamed = Object.keys(localState).find( + (k) => localState[k].identifier === this.#originalState[key].identifier + ); + nameMap[key] = renamed; + } + + // Remove identifiers + for (let key in localState) delete localState[key].identifier; + + // Local state is the source of truth + this.info.globalState.subjects = localState; + + const { results, subjects } = this.info.globalState; + + const sourceDataObject = Object.keys(this.info.globalState.interfaces).reduce((acc, key) => { + acc[key] = {}; + return acc; + }, {}); + + // Modify the results object to track new subjects / sessions + updateResultsFromSubjects(results, subjects, sourceDataObject, nameMap); // NOTE: This directly mutates the results object + }; + + footer = { + onNext: () => { + const extraElements = document.querySelectorAll(".HandsontableCopyPaste"); + extraElements.forEach((element) => element.remove()); + return this.to(1); + }, + }; + + updated() { + super.updated(); // Call if updating data + } + + #globalModal; + + connectedCallback() { + super.connectedCallback(); + + const schema = preprocessMetadataSchema(undefined, true).properties.Subject; + + const modal = (this.#globalModal = createGlobalFormModal.call(this, { + header: "Global Subject Metadata", + key: "Subject", + validateEmptyValues: null, + schema, + formProps: { + validateOnChange: (localPath, parent, path) => { + return validateOnChange(localPath, parent, ["Subject", ...path]); + }, + }, + })); + document.body.append(modal); + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this.#globalModal) this.#globalModal.remove(); + } + + #originalState = {}; + + render() { + const hasMultipleSessions = this.workflow.multiple_sessions.value; + + const subjects = (this.localState = structuredClone(this.info.globalState.subjects ?? {})); + + // Ensure all the proper subjects are in the global state + const toHave = Object.keys(this.info.globalState.results); + const toRemove = Object.keys(subjects).filter((sub) => !toHave.includes(sub)); + toRemove.forEach((sub) => delete subjects[sub]); + toHave.forEach((sub) => (subjects[sub] = subjects[sub] ?? {})); + + this.#originalState = structuredClone(subjects); + + for (let subject in subjects) { + const sessions = Object.keys(this.info.globalState.results[subject]); + subjects[subject].sessions = sessions; + subjects[subject].identifier = this.#originalState[subject].identifier = Symbol("subject"); // Add identifier to subject + } + + const contextMenuConfig = { ignore: ["row_below"] }; + + if (!hasMultipleSessions) contextMenuConfig.ignore.push("remove_row"); + + this.table = new Table({ + schema: { + type: "array", + items: getSubjectSchema(), + }, + data: subjects, + globals: hasMultipleSessions ? this.info.globalState.project.Subject : undefined, + keyColumn: "subject_id", + validateEmptyCells: ["subject_id", "sessions"], + contextMenu: contextMenuConfig, + groups: [ + [ + "sex", + "species", + // 'age' + ], // Validate both when one is changed + ], + onThrow: (message, type) => this.notify(message, type), + onOverride: (name) => { + this.notify(`${header(name)} has been overridden with a global value.`, "warning", 3000); + }, + onUpdate: () => { + this.unsavedUpdates = "conversions"; + }, + validateOnChange: function (localPath, parent, v) { + const name = localPath[localPath.length - 1]; + + if (name === "sessions") { + if (v?.length) return true; + else { + return [ + { + message: "Sessions must have at least one entry", + type: "error", + }, + ]; + } + } else { + delete parent.sessions; // Delete sessions from parent copy + return validateOnChange.call(this, name, parent, ["Subject", ...localPath.slice(0, -1)], v); + } + }, + }); + + return html` +
+
${this.table}
+ ${this.#addButton} +
+ `; + } +} + +customElements.get("nwbguide-guided-subjects-page") || + customElements.define("nwbguide-guided-subjects-page", GuidedSubjectsPage); diff --git a/src/electron/renderer/src/stories/pages/settings/SettingsPage.js b/src/electron/renderer/src/stories/pages/settings/SettingsPage.js index 9f72a1676..e314a9a41 100644 --- a/src/electron/renderer/src/stories/pages/settings/SettingsPage.js +++ b/src/electron/renderer/src/stories/pages/settings/SettingsPage.js @@ -1,345 +1,345 @@ -import { html } from "lit"; -import { JSONSchemaForm } from "../../JSONSchemaForm.js"; -import { Page } from "../Page.js"; -import { onThrow } from "../../../errors"; -import dandiGlobalSchema from "../../../../../../schemas/json/dandi/global.json"; -import projectGlobalSchema from "../../../../../../schemas/json/project/globals.json" assert { type: "json" }; -import developerGlobalSchema from "../../../../../../schemas/json/developer/globals.json" assert { type: "json" }; - -import { validateDANDIApiKey } from "../../../validation/dandi"; - -import { Button } from "../../Button.js"; -import { global, remove, save } from "../../../progress/index.js"; -import { merge, setUndefinedIfNotDeclared } from "../utils"; - -import { homeDirectory, notyf, testDataFolderPath } from "../../../dependencies/globals"; -import { SERVER_FILE_PATH, electron, path, port, fs } from "../../../electron/index.js"; - -import saveSVG from "../../../../assets/icons/save.svg?raw"; -import folderSVG from "../../../../assets/icons/folder_open.svg?raw"; -import deleteSVG from "../../../../assets/icons/delete.svg?raw"; -import generateSVG from "../../../../assets/icons/restart.svg?raw"; - -import { header } from "../../forms/utils"; - -import examplePipelines from "../../../../../../example_pipelines.yml"; -import { run } from "../guided-mode/options/utils.js"; -import { joinPath } from "../../../globals"; - -const DATA_OUTPUT_PATH = joinPath(testDataFolderPath, "single_session_data"); -const DATASET_OUTPUT_PATH = joinPath(testDataFolderPath, "multi_session_dataset"); - -const propertiesToTransform = ["folder_path", "file_path", "config_file_path"]; - -const deleteIfExists = (path) => (fs.existsSync(path) ? fs.rmSync(path, { recursive: true }) : ""); - -function saveNewPipelineFromYaml(name, info, rootFolder) { - const subject_id = "mouse1"; - const sessions = ["session1"]; - const session_id = sessions[0]; - - info = structuredClone(info); // Copy info - - const hasMultipleSessions = sessions.length > 1; - - const resolvedInterfaces = info.interfaces ?? info; - - Object.values(resolvedInterfaces).forEach((info) => { - propertiesToTransform.forEach((property) => { - if (info[property]) { - const fullPath = path.join(rootFolder, info[property]); - if (fs.existsSync(fullPath)) info[property] = fullPath; - else throw new Error("Source data not available for this pipeline."); - } - }); - }); - - const resolvedMetadata = { - NWBFile: { session_id }, - Subject: { subject_id }, - }; - - resolvedMetadata.__generated = structuredClone(info.interfaces ? info.metadata ?? {} : {}); - - const resolvedInfo = { - source_data: resolvedInterfaces, - metadata: resolvedMetadata, - }; - - const updatedName = header(name); - - remove(updatedName, true); - - const workflowInfo = { - multiple_sessions: hasMultipleSessions, - }; - - if (!workflowInfo.multiple_sessions) { - workflowInfo.subject_id = subject_id; - workflowInfo.session_id = session_id; - } - - save({ - info: { - globalState: { - project: { - name: updatedName, - initialized: true, - workflow: workflowInfo, - }, - - // provide data for all supported interfaces - interfaces: Object.keys(resolvedInterfaces).reduce((acc, key) => { - acc[key] = `${key}`; - return acc; - }, {}), - - structure: {}, - - results: { - [subject_id]: sessions.reduce((acc, sessionId) => { - acc[session_id] = resolvedInfo; - return acc; - }, {}), - }, - - subjects: { - [subject_id]: { - sessions: sessions, - sex: "M", - species: "Mus musculus", - age: "P30D", - }, - }, - }, - }, - }); -} - -const schema = merge( - projectGlobalSchema, - { - properties: { - DANDI: { - title: "DANDI Settings", - ...dandiGlobalSchema, - }, - developer: { - title: "Developer Settings", - ...developerGlobalSchema, - }, - }, - required: ["DANDI", "developer"], - }, - { - arrays: "append", - } -); - -export class SettingsPage extends Page { - header = { - title: "App Settings", - subtitle: "This page allows you to set global settings for the GUIDE.", - controls: [ - new Button({ - icon: saveSVG, - onClick: async () => { - if (!this.unsavedUpdates) return this.#openNotyf("All changes were already saved", "success"); - this.save(); - }, - }), - ], - }; - - constructor(...args) { - super(...args); - this.style.height = "100%"; // Fix main section - } - - #notification; - - #openNotyf = (message, type) => { - if (this.#notification) notyf.dismiss(this.#notification); - return (this.#notification = this.notify(message, type)); - }; - - deleteTestData = () => { - deleteIfExists(DATA_OUTPUT_PATH); - deleteIfExists(DATASET_OUTPUT_PATH); - }; - - generateTestData = async () => { - if (!fs.existsSync(DATA_OUTPUT_PATH)) { - await run( - "generate", - { - output_path: DATA_OUTPUT_PATH, - }, - { - title: "Generating test data", - html: "This will take several minutes to complete.", - base: "data", - } - ).catch((error) => { - this.notify(error.message, "error"); - throw error; - }); - } - - await run( - "generate/dataset", - { - input_path: DATA_OUTPUT_PATH, - output_path: DATASET_OUTPUT_PATH, - }, - { - title: "Generating test dataset", - base: "data", - } - ).catch((error) => { - this.notify(error.message, "error"); - throw error; - }); - - const sanitizedOutputPath = DATASET_OUTPUT_PATH.replace(homeDirectory, "~"); - - this.notify(`Test dataset successfully generated at ${sanitizedOutputPath}!`); - - return DATASET_OUTPUT_PATH; - }; - - beforeSave = async () => { - const { resolved } = this.form; - setUndefinedIfNotDeclared(schema.properties, resolved); - - merge(resolved, global.data); - - global.save(); // Save the changes, even if invalid on the form - this.#openNotyf(`Global settings changes saved.`, "success"); - }; - - render() { - this.localState = structuredClone(global.data); - - // NOTE: API Keys and Dandiset IDs persist across selected project - this.form = new JSONSchemaForm({ - results: this.localState, - schema, - onUpdate: () => (this.unsavedUpdates = true), - validateOnChange: async (name, parent) => { - const value = parent[name]; - if (name.includes("api_key")) return await validateDANDIApiKey(value, name.includes("staging")); - return true; - }, - onThrow, - }); - - const generatePipelineButton = new Button({ - label: "Generate Example Pipelines", - onClick: async () => { - const { testing_data_folder } = this.form.results.developer ?? {}; - - if (!testing_data_folder) - return this.#openNotyf( - `Please specify a testing data folder in the Developer section before attempting to generate pipelines.`, - "error" - ); - - const pipelineNames = Object.keys(examplePipelines); - - const resolved = pipelineNames.reverse().map((name) => { - try { - saveNewPipelineFromYaml(name, examplePipelines[name], testing_data_folder); - return true; - } catch (e) { - console.error(e); - return name; - } - }); - - const nSuccessful = resolved.reduce((acc, v) => (acc += v === true ? 1 : 0), 0); - const nFailed = resolved.length - nSuccessful; - - if (nFailed) { - const failDisplay = - nFailed === 1 - ? `the ${resolved.find((v) => typeof v === "string")} pipeline` - : `${nFailed} pipelines`; - this.#openNotyf( - `

Generated ${nSuccessful} test pipelines.

Could not find source data for ${failDisplay}.`, - "warning" - ); - } else if (nSuccessful) this.#openNotyf(`Generated ${nSuccessful} test pipelines.`, "success"); - else - this.#openNotyf( - `

Pipeline Generation Failed

Could not find source data for any pipelines.`, - "error" - ); - }, - }); - - setTimeout(() => { - const testFolderInput = this.form.getFormElement(["developer", "testing_data_folder"]); - testFolderInput.after(generatePipelineButton); - }, 100); - - return html` -
-
-

Server Port: ${port}

-

Server File Location: ${SERVER_FILE_PATH}

-
-
-

Test Dataset

-
- ${fs.existsSync(DATASET_OUTPUT_PATH) && fs.existsSync(DATA_OUTPUT_PATH) - ? [ - new Button({ - icon: deleteSVG, - label: "Delete", - size: "small", - onClick: async () => { - this.deleteTestData(); - this.notify(`Test dataset successfully deleted from your system.`); - this.requestUpdate(); - }, - }), - - new Button({ - icon: folderSVG, - label: "Open", - size: "small", - onClick: async () => { - if (electron.ipcRenderer) { - if (fs.existsSync(DATASET_OUTPUT_PATH)) - electron.ipcRenderer.send("showItemInFolder", DATASET_OUTPUT_PATH); - else { - this.notify("The test dataset no longer exists!", "warning"); - this.requestUpdate(); - } - } - }, - }), - ] - : new Button({ - label: "Generate", - icon: generateSVG, - size: "small", - onClick: async () => { - const output_path = await this.generateTestData(); - if (electron.ipcRenderer) - electron.ipcRenderer.send("showItemInFolder", output_path); - this.requestUpdate(); - }, - })} -
-
-
-
-
- ${this.form} - `; - } -} - -customElements.get("nwbguide-settings-page") || customElements.define("nwbguide-settings-page", SettingsPage); +import { html } from "lit"; +import { JSONSchemaForm } from "../../JSONSchemaForm.js"; +import { Page } from "../Page.js"; +import { onThrow } from "../../../errors"; +import dandiGlobalSchema from "../../../../../../schemas/json/dandi/global.json"; +import projectGlobalSchema from "../../../../../../schemas/json/project/globals.json" assert { type: "json" }; +import developerGlobalSchema from "../../../../../../schemas/json/developer/globals.json" assert { type: "json" }; + +import { validateDANDIApiKey } from "../../../validation/dandi"; + +import { Button } from "../../Button.js"; +import { global, remove, save } from "../../../progress/index.js"; +import { merge, setUndefinedIfNotDeclared } from "../utils"; + +import { homeDirectory, notyf, testDataFolderPath } from "../../../dependencies/globals"; +import { SERVER_FILE_PATH, electron, path, port, fs } from "../../../electron/index.js"; + +import saveSVG from "../../../../assets/icons/save.svg?raw"; +import folderSVG from "../../../../assets/icons/folder_open.svg?raw"; +import deleteSVG from "../../../../assets/icons/delete.svg?raw"; +import generateSVG from "../../../../assets/icons/restart.svg?raw"; + +import { header } from "../../forms/utils"; + +import examplePipelines from "../../../../../../example_pipelines.yml"; +import { run } from "../guided-mode/options/utils.js"; +import { joinPath } from "../../../globals"; + +const DATA_OUTPUT_PATH = joinPath(testDataFolderPath, "single_session_data"); +const DATASET_OUTPUT_PATH = joinPath(testDataFolderPath, "multi_session_dataset"); + +const propertiesToTransform = ["folder_path", "file_path", "config_file_path"]; + +const deleteIfExists = (path) => (fs.existsSync(path) ? fs.rmSync(path, { recursive: true }) : ""); + +function saveNewPipelineFromYaml(name, info, rootFolder) { + const subject_id = "mouse1"; + const sessions = ["session1"]; + const session_id = sessions[0]; + + info = structuredClone(info); // Copy info + + const hasMultipleSessions = sessions.length > 1; + + const resolvedInterfaces = info.interfaces ?? info; + + Object.values(resolvedInterfaces).forEach((info) => { + propertiesToTransform.forEach((property) => { + if (info[property]) { + const fullPath = path.join(rootFolder, info[property]); + if (fs.existsSync(fullPath)) info[property] = fullPath; + else throw new Error("Source data not available for this pipeline."); + } + }); + }); + + const resolvedMetadata = { + NWBFile: { session_id }, + Subject: { subject_id }, + }; + + resolvedMetadata.__generated = structuredClone(info.interfaces ? info.metadata ?? {} : {}); + + const resolvedInfo = { + source_data: resolvedInterfaces, + metadata: resolvedMetadata, + }; + + const updatedName = header(name); + + remove(updatedName, true); + + const workflowInfo = { + multiple_sessions: hasMultipleSessions, + }; + + if (!workflowInfo.multiple_sessions) { + workflowInfo.subject_id = subject_id; + workflowInfo.session_id = session_id; + } + + save({ + info: { + globalState: { + project: { + name: updatedName, + initialized: true, + workflow: workflowInfo, + }, + + // provide data for all supported interfaces + interfaces: Object.keys(resolvedInterfaces).reduce((acc, key) => { + acc[key] = `${key}`; + return acc; + }, {}), + + structure: {}, + + results: { + [subject_id]: sessions.reduce((acc, sessionId) => { + acc[session_id] = resolvedInfo; + return acc; + }, {}), + }, + + subjects: { + [subject_id]: { + sessions: sessions, + sex: "M", + species: "Mus musculus", + age: "P30D", + }, + }, + }, + }, + }); +} + +const schema = merge( + projectGlobalSchema, + { + properties: { + DANDI: { + title: "DANDI Settings", + ...dandiGlobalSchema, + }, + developer: { + title: "Developer Settings", + ...developerGlobalSchema, + }, + }, + required: ["DANDI", "developer"], + }, + { + arrays: "append", + } +); + +export class SettingsPage extends Page { + header = { + title: "App Settings", + subtitle: "This page allows you to set global settings for the GUIDE.", + controls: [ + new Button({ + icon: saveSVG, + onClick: async () => { + if (!this.unsavedUpdates) return this.#openNotyf("All changes were already saved", "success"); + this.save(); + }, + }), + ], + }; + + constructor(...args) { + super(...args); + this.style.height = "100%"; // Fix main section + } + + #notification; + + #openNotyf = (message, type) => { + if (this.#notification) notyf.dismiss(this.#notification); + return (this.#notification = this.notify(message, type)); + }; + + deleteTestData = () => { + deleteIfExists(DATA_OUTPUT_PATH); + deleteIfExists(DATASET_OUTPUT_PATH); + }; + + generateTestData = async () => { + if (!fs.existsSync(DATA_OUTPUT_PATH)) { + await run( + "generate", + { + output_path: DATA_OUTPUT_PATH, + }, + { + title: "Generating test data", + html: "This will take several minutes to complete.", + base: "data", + } + ).catch((error) => { + this.notify(error.message, "error"); + throw error; + }); + } + + await run( + "generate/dataset", + { + input_path: DATA_OUTPUT_PATH, + output_path: DATASET_OUTPUT_PATH, + }, + { + title: "Generating test dataset", + base: "data", + } + ).catch((error) => { + this.notify(error.message, "error"); + throw error; + }); + + const sanitizedOutputPath = DATASET_OUTPUT_PATH.replace(homeDirectory, "~"); + + this.notify(`Test dataset successfully generated at ${sanitizedOutputPath}!`); + + return DATASET_OUTPUT_PATH; + }; + + beforeSave = async () => { + const { resolved } = this.form; + setUndefinedIfNotDeclared(schema.properties, resolved); + + merge(resolved, global.data); + + global.save(); // Save the changes, even if invalid on the form + this.#openNotyf(`Global settings changes saved.`, "success"); + }; + + render() { + this.localState = structuredClone(global.data); + + // NOTE: API Keys and Dandiset IDs persist across selected project + this.form = new JSONSchemaForm({ + results: this.localState, + schema, + onUpdate: () => (this.unsavedUpdates = true), + validateOnChange: async (name, parent) => { + const value = parent[name]; + if (name.includes("api_key")) return await validateDANDIApiKey(value, name.includes("staging")); + return true; + }, + onThrow, + }); + + const generatePipelineButton = new Button({ + label: "Generate Example Pipelines", + onClick: async () => { + const { testing_data_folder } = this.form.results.developer ?? {}; + + if (!testing_data_folder) + return this.#openNotyf( + `Please specify a testing data folder in the Developer section before attempting to generate pipelines.`, + "error" + ); + + const pipelineNames = Object.keys(examplePipelines); + + const resolved = pipelineNames.reverse().map((name) => { + try { + saveNewPipelineFromYaml(name, examplePipelines[name], testing_data_folder); + return true; + } catch (e) { + console.error(e); + return name; + } + }); + + const nSuccessful = resolved.reduce((acc, v) => (acc += v === true ? 1 : 0), 0); + const nFailed = resolved.length - nSuccessful; + + if (nFailed) { + const failDisplay = + nFailed === 1 + ? `the ${resolved.find((v) => typeof v === "string")} pipeline` + : `${nFailed} pipelines`; + this.#openNotyf( + `

Generated ${nSuccessful} test pipelines.

Could not find source data for ${failDisplay}.`, + "warning" + ); + } else if (nSuccessful) this.#openNotyf(`Generated ${nSuccessful} test pipelines.`, "success"); + else + this.#openNotyf( + `

Pipeline Generation Failed

Could not find source data for any pipelines.`, + "error" + ); + }, + }); + + setTimeout(() => { + const testFolderInput = this.form.getFormElement(["developer", "testing_data_folder"]); + testFolderInput.after(generatePipelineButton); + }, 100); + + return html` +
+
+

Server Port: ${port}

+

Server File Location: ${SERVER_FILE_PATH}

+
+
+

Test Dataset

+
+ ${fs.existsSync(DATASET_OUTPUT_PATH) && fs.existsSync(DATA_OUTPUT_PATH) + ? [ + new Button({ + icon: deleteSVG, + label: "Delete", + size: "small", + onClick: async () => { + this.deleteTestData(); + this.notify(`Test dataset successfully deleted from your system.`); + this.requestUpdate(); + }, + }), + + new Button({ + icon: folderSVG, + label: "Open", + size: "small", + onClick: async () => { + if (electron.ipcRenderer) { + if (fs.existsSync(DATASET_OUTPUT_PATH)) + electron.ipcRenderer.send("showItemInFolder", DATASET_OUTPUT_PATH); + else { + this.notify("The test dataset no longer exists!", "warning"); + this.requestUpdate(); + } + } + }, + }), + ] + : new Button({ + label: "Generate", + icon: generateSVG, + size: "small", + onClick: async () => { + const output_path = await this.generateTestData(); + if (electron.ipcRenderer) + electron.ipcRenderer.send("showItemInFolder", output_path); + this.requestUpdate(); + }, + })} +
+
+
+
+
+ ${this.form} + `; + } +} + +customElements.get("nwbguide-settings-page") || customElements.define("nwbguide-settings-page", SettingsPage); diff --git a/src/electron/renderer/src/stories/pages/uploads/UploadsPage.js b/src/electron/renderer/src/stories/pages/uploads/UploadsPage.js index c0ab9e601..434f830c3 100644 --- a/src/electron/renderer/src/stories/pages/uploads/UploadsPage.js +++ b/src/electron/renderer/src/stories/pages/uploads/UploadsPage.js @@ -1,445 +1,445 @@ -import { html } from "lit"; -import { until } from "lit/directives/until.js"; - -import { JSONSchemaForm } from "../../JSONSchemaForm.js"; -import { Page } from "../Page.js"; -import { onThrow } from "../../../errors"; - -const folderPathKey = "filesystem_paths"; -import dandiUploadSchema, { - addDandiset, - ready, - regenerateDandisets, -} from "../../../../../../schemas/dandi-upload.schema"; -import dandiStandaloneSchema from "../../../../../../schemas/json/dandi/standalone.json"; -const dandiSchema = merge(dandiUploadSchema, structuredClone(dandiStandaloneSchema), { arrays: "append" }); - -import dandiCreateSchema from "../../../../../../schemas/dandi-create.schema"; - -import { Button } from "../../Button.js"; -import { global } from "../../../progress/index.js"; -import { merge } from "../utils"; - -import { run } from "../guided-mode/options/utils.js"; -import { Modal } from "../../Modal"; -import { DandiResults } from "../../DandiResults.js"; - -import dandiGlobalSchema from "../../../../../../schemas/json/dandi/global.json"; -import { JSONSchemaInput } from "../../JSONSchemaInput.js"; -import { header } from "../../forms/utils"; - -import { validateDANDIApiKey } from "../../../validation/dandi"; - -import * as dandi from "dandi"; - -import keyIcon from "../../../../assets/icons/key.svg?raw"; - -import { AWARD_VALIDATION_FAIL_MESSAGE, awardNumberValidator, isStaging, validate } from "./utils"; -import { createFormModal } from "../../forms/GlobalFormModal"; - -export async function createDandiset(results = {}) { - let notification; - - const notify = (message, type) => { - if (notification) this.dismiss(notification); - return (notification = this.notify(message, type)); - }; - - const modal = new Modal({ - header: "Create a Dandiset", - }); - - const content = document.createElement("div"); - Object.assign(content.style, { - padding: "25px", - }); - - const updateNIHInput = (state) => { - const nihInput = form.getFormElement(["nih_award_number"]); - - const isEmbargoed = !!state; - - // Show the NIH input if embargo is set - if (isEmbargoed) nihInput.removeAttribute("hidden"); - else nihInput.setAttribute("hidden", ""); - - // Make the NIH input required if embargo is set - nihInput.required = isEmbargoed; - }; - - const form = new JSONSchemaForm({ - schema: dandiCreateSchema, - results, - validateEmptyValues: false, // Only show errors after submission - validateOnChange: async (name, parent) => { - const value = parent[name]; - - if (name === "embargo_status") return updateNIHInput(value); - - if (name === "nih_award_number") { - if (value) - return awardNumberValidator(value) || [{ type: "error", message: AWARD_VALIDATION_FAIL_MESSAGE }]; - else if (parent["embargo_status"]) - return [ - { - type: "error", - message: "You must provide an NIH Award Number to embargo your data.", - }, - ]; - } - }, - groups: [ - { - name: "Embargo your Data", - properties: [["embargo_status"], ["nih_award_number"]], - }, - ], - }); - - content.append(form); - modal.append(content); - - modal.onClose = async () => notify("Dandiset was not created.", "error"); - - return new Promise((resolve) => { - const button = new Button({ - label: "Create", - primary: true, - onClick: async () => { - await form.validate().catch(() => { - const message = "Please fill out all required fields"; - notify("Dandiset was not set", "error"); - throw message; - }); - - const uploadToMain = form.resolved.archive === "main"; - const staging = !uploadToMain; - - const api_key = await getAPIKey.call(this, staging); - - const api = new dandi.API({ token: api_key, type: staging ? "staging" : undefined }); - await api.init(); - - const metadata = { - description: form.resolved.description, - license: form.resolved.license, - }; - - if (form.resolved.nih_award_number) { - metadata.contributor = [ - { - name: "National Institutes of Health (NIH)", - roleName: ["dcite:Funder"], - schemaKey: "Organization", - awardNumber: form.resolved.nih_award_number, - }, - ]; - } - - const res = await api.create(form.resolved.title, metadata, form.resolved.embargo_status); - - const id = res.identifier; - - notify(`Dandiset ${id} was created`, "success"); - - await addDandiset(res); - - const input = this.form.getFormElement(["dandiset"]); - input.updateData(id); - input.requestUpdate(); - - this.save(); - - resolve(res); - }, - }); - - modal.footer = button; - - modal.open = true; - - document.body.append(modal); - }).finally(() => { - modal.remove(); - }); -} - -async function getAPIKey(staging = false) { - const whichAPIKey = staging ? "staging_api_key" : "main_api_key"; - const DANDI = global.data.DANDI; - let api_key = DANDI?.api_keys?.[whichAPIKey]; - - const errors = await validateDANDIApiKey(api_key, staging); - - const isInvalid = !errors || errors.length; - - if (isInvalid) { - const modal = new Modal({ - header: `${api_key ? "Update" : "Provide"} your ${header(whichAPIKey)}`, - open: true, - }); - - const input = new JSONSchemaInput({ - path: [whichAPIKey], - schema: dandiGlobalSchema.properties.api_keys.properties[whichAPIKey], - }); - - input.style.padding = "25px"; - - modal.append(input); - - let notification; - - const notify = (message, type) => { - if (notification) this.dismiss(notification); - return (notification = this.notify(message, type)); - }; - - modal.onClose = async () => notify("The updated DANDI API key was not set", "error"); - - api_key = await new Promise((resolve) => { - const button = new Button({ - label: "Save", - primary: true, - onClick: async () => { - const value = input.value; - if (value) { - const errors = await validateDANDIApiKey(input.value, staging); - if (!errors || !errors.length) { - modal.remove(); - - merge( - { - DANDI: { - api_keys: { - [whichAPIKey]: value, - }, - }, - }, - global.data - ); - - global.save(); - resolve(value); - } else { - notify(errors[0].message, "error"); - return false; - } - } else { - notify("Your DANDI API key was not set", "error"); - } - }, - }); - - modal.footer = button; - - document.body.append(modal); - }); - } - - return api_key; -} - -export async function uploadToDandi(info, type = "project" in info ? "project" : "") { - const { dandiset } = info; - - const dandiset_id = dandiset; - - const staging = isStaging(dandiset_id); // Automatically detect staging IDs - - const api_key = await getAPIKey.call(this, staging); - - const payload = { - dandiset_id, - ...info.additional_settings, - staging, - api_key, - }; - - if (info.project) payload.project = info.project; - else payload.filesystem_paths = info.filesystem_paths; - - const result = await run(type ? `upload/${type}` : "upload", payload, { - title: "Uploading your files to DANDI", - }).catch((error) => { - this.notify(error.message, "error"); - throw error; - }); - - if (result) - this.notify( - `${ - info.project ?? `${info[folderPathKey].length} filesystem entries` - } successfully uploaded to Dandiset ${dandiset_id}`, - "success" - ); - - return result; -} - -export class UploadsPage extends Page { - header = { - title: "NWB File Uploads", - subtitle: "Upload folders and individual NWB files to the DANDI Archive.", - controls: [ - new Button({ - icon: keyIcon, - label: "API Keys", - onClick: () => { - this.#globalModal.form.results = structuredClone(global.data.DANDI?.api_keys ?? {}); - this.#globalModal.open = true; - }, - }), - ], - }; - - constructor(...args) { - super(...args); - } - - #globalModal = null; - - #saveNotification; - connectedCallback() { - super.connectedCallback(); - - const modal = (this.#globalModal = createFormModal.call(this, { - header: "DANDI API Keys", - schema: dandiGlobalSchema.properties.api_keys, - onSave: async (form) => { - if (this.#saveNotification) this.dismiss(this.#saveNotification); - - const apiKeys = form.resolved; - if (!Object.keys(apiKeys).length) { - this.#saveNotification = this.notify("No API keys were provided", "error"); - return null; - } - - const globalDandiData = global.data.DANDI ?? (global.data.DANDI = {}); - if (!globalDandiData.api_keys) globalDandiData.api_keys = {}; - merge(apiKeys, globalDandiData.api_keys); - - global.save(); - await regenerateDandisets(); - const input = this.form.getFormElement(["dandisets"]); - input.requestUpdate(); - }, - formProps: { - validateOnChange: async (name, parent) => { - const value = parent[name]; - if (name.includes("api_key")) return await validateDANDIApiKey(value, name.includes("staging")); - }, - }, - })); - document.body.append(modal); - } - - disconnectedCallback() { - super.disconnectedCallback(); - if (this.#globalModal) this.#globalModal.remove(); - } - - render() { - const globalState = (global.data.uploads = global.data.uploads ?? {}); - const defaultButtonMessage = "Upload Files"; - - const button = new Button({ - label: defaultButtonMessage, - onClick: async () => { - await this.form.validate(); // Will throw an error in the callback - - const results = await uploadToDandi.call(this, { ...global.data.uploads }); - global.data.uploads = {}; - global.save(); - - const modal = new Modal({ open: true }); - modal.header = "DANDI Upload Summary"; - const summary = new DandiResults({ - id: globalState.dandiset, - files: { - subject: results.map((file) => { - return { file }; - }), - }, - }); - summary.style.padding = "25px"; - modal.append(summary); - - document.body.append(modal); - - this.requestUpdate(); - }, - }); - - const promise = ready.cpus - .then(() => ready.dandisets) - .then(() => { - // NOTE: API Keys and Dandiset IDs persist across selected project - return (this.form = new JSONSchemaForm({ - results: globalState, - schema: dandiSchema, - validateEmptyValues: false, - controls: { - dandiset: [ - new Button({ - label: "Create New Dandiset", - buttonStyles: { - width: "max-content", - }, - onClick: async () => { - await createDandiset.call(this, { title: this.form.resolved.dandiset }); - this.requestUpdate(); - }, - }), - ], - }, - sort: ([k1]) => { - if (k1 === folderPathKey) return -1; - }, - onUpdate: ([id]) => { - if (id === folderPathKey) { - const keysToUpdate = ["dandiset"]; - keysToUpdate.forEach((k) => { - const input = this.form.getFormElement([k]); - if (input.value) input.updateData(""); - }); - } - - global.save(); - }, - - onThrow, - - transformErrors: (error) => { - if (error.message === "Filesystem Paths is a required property.") - error.message = "Please select at least one file or folder to upload."; - }, - - validateOnChange: validate, - })); - }) - .catch((error) => html`

${error}

`); - - // Confirm that one api key exists - promise.then(() => { - const api_keys = global.data.DANDI?.api_keys; - if (!api_keys || !Object.keys(api_keys).length) this.#globalModal.open = true; - }); - - return html` - ${until( - promise.then((form) => { - return html` - ${form} -
- ${button} - `; - }), - html`

Loading form contents...

-

` - )} - `; - } -} - -customElements.get("nwbguide-uploads-page") || customElements.define("nwbguide-uploads-page", UploadsPage); +import { html } from "lit"; +import { until } from "lit/directives/until.js"; + +import { JSONSchemaForm } from "../../JSONSchemaForm.js"; +import { Page } from "../Page.js"; +import { onThrow } from "../../../errors"; + +const folderPathKey = "filesystem_paths"; +import dandiUploadSchema, { + addDandiset, + ready, + regenerateDandisets, +} from "../../../../../../schemas/dandi-upload.schema"; +import dandiStandaloneSchema from "../../../../../../schemas/json/dandi/standalone.json"; +const dandiSchema = merge(dandiUploadSchema, structuredClone(dandiStandaloneSchema), { arrays: "append" }); + +import dandiCreateSchema from "../../../../../../schemas/dandi-create.schema"; + +import { Button } from "../../Button.js"; +import { global } from "../../../progress/index.js"; +import { merge } from "../utils"; + +import { run } from "../guided-mode/options/utils.js"; +import { Modal } from "../../Modal"; +import { DandiResults } from "../../DandiResults.js"; + +import dandiGlobalSchema from "../../../../../../schemas/json/dandi/global.json"; +import { JSONSchemaInput } from "../../JSONSchemaInput.js"; +import { header } from "../../forms/utils"; + +import { validateDANDIApiKey } from "../../../validation/dandi"; + +import * as dandi from "dandi"; + +import keyIcon from "../../../../assets/icons/key.svg?raw"; + +import { AWARD_VALIDATION_FAIL_MESSAGE, awardNumberValidator, isStaging, validate } from "./utils"; +import { createFormModal } from "../../forms/GlobalFormModal"; + +export async function createDandiset(results = {}) { + let notification; + + const notify = (message, type) => { + if (notification) this.dismiss(notification); + return (notification = this.notify(message, type)); + }; + + const modal = new Modal({ + header: "Create a Dandiset", + }); + + const content = document.createElement("div"); + Object.assign(content.style, { + padding: "25px", + }); + + const updateNIHInput = (state) => { + const nihInput = form.getFormElement(["nih_award_number"]); + + const isEmbargoed = !!state; + + // Show the NIH input if embargo is set + if (isEmbargoed) nihInput.removeAttribute("hidden"); + else nihInput.setAttribute("hidden", ""); + + // Make the NIH input required if embargo is set + nihInput.required = isEmbargoed; + }; + + const form = new JSONSchemaForm({ + schema: dandiCreateSchema, + results, + validateEmptyValues: false, // Only show errors after submission + validateOnChange: async (name, parent) => { + const value = parent[name]; + + if (name === "embargo_status") return updateNIHInput(value); + + if (name === "nih_award_number") { + if (value) + return awardNumberValidator(value) || [{ type: "error", message: AWARD_VALIDATION_FAIL_MESSAGE }]; + else if (parent["embargo_status"]) + return [ + { + type: "error", + message: "You must provide an NIH Award Number to embargo your data.", + }, + ]; + } + }, + groups: [ + { + name: "Embargo your Data", + properties: [["embargo_status"], ["nih_award_number"]], + }, + ], + }); + + content.append(form); + modal.append(content); + + modal.onClose = async () => notify("Dandiset was not created.", "error"); + + return new Promise((resolve) => { + const button = new Button({ + label: "Create", + primary: true, + onClick: async () => { + await form.validate().catch(() => { + const message = "Please fill out all required fields"; + notify("Dandiset was not set", "error"); + throw message; + }); + + const uploadToMain = form.resolved.archive === "main"; + const staging = !uploadToMain; + + const api_key = await getAPIKey.call(this, staging); + + const api = new dandi.API({ token: api_key, type: staging ? "staging" : undefined }); + await api.init(); + + const metadata = { + description: form.resolved.description, + license: form.resolved.license, + }; + + if (form.resolved.nih_award_number) { + metadata.contributor = [ + { + name: "National Institutes of Health (NIH)", + roleName: ["dcite:Funder"], + schemaKey: "Organization", + awardNumber: form.resolved.nih_award_number, + }, + ]; + } + + const res = await api.create(form.resolved.title, metadata, form.resolved.embargo_status); + + const id = res.identifier; + + notify(`Dandiset ${id} was created`, "success"); + + await addDandiset(res); + + const input = this.form.getFormElement(["dandiset"]); + input.updateData(id); + input.requestUpdate(); + + this.save(); + + resolve(res); + }, + }); + + modal.footer = button; + + modal.open = true; + + document.body.append(modal); + }).finally(() => { + modal.remove(); + }); +} + +async function getAPIKey(staging = false) { + const whichAPIKey = staging ? "staging_api_key" : "main_api_key"; + const DANDI = global.data.DANDI; + let api_key = DANDI?.api_keys?.[whichAPIKey]; + + const errors = await validateDANDIApiKey(api_key, staging); + + const isInvalid = !errors || errors.length; + + if (isInvalid) { + const modal = new Modal({ + header: `${api_key ? "Update" : "Provide"} your ${header(whichAPIKey)}`, + open: true, + }); + + const input = new JSONSchemaInput({ + path: [whichAPIKey], + schema: dandiGlobalSchema.properties.api_keys.properties[whichAPIKey], + }); + + input.style.padding = "25px"; + + modal.append(input); + + let notification; + + const notify = (message, type) => { + if (notification) this.dismiss(notification); + return (notification = this.notify(message, type)); + }; + + modal.onClose = async () => notify("The updated DANDI API key was not set", "error"); + + api_key = await new Promise((resolve) => { + const button = new Button({ + label: "Save", + primary: true, + onClick: async () => { + const value = input.value; + if (value) { + const errors = await validateDANDIApiKey(input.value, staging); + if (!errors || !errors.length) { + modal.remove(); + + merge( + { + DANDI: { + api_keys: { + [whichAPIKey]: value, + }, + }, + }, + global.data + ); + + global.save(); + resolve(value); + } else { + notify(errors[0].message, "error"); + return false; + } + } else { + notify("Your DANDI API key was not set", "error"); + } + }, + }); + + modal.footer = button; + + document.body.append(modal); + }); + } + + return api_key; +} + +export async function uploadToDandi(info, type = "project" in info ? "project" : "") { + const { dandiset } = info; + + const dandiset_id = dandiset; + + const staging = isStaging(dandiset_id); // Automatically detect staging IDs + + const api_key = await getAPIKey.call(this, staging); + + const payload = { + dandiset_id, + ...info.additional_settings, + staging, + api_key, + }; + + if (info.project) payload.project = info.project; + else payload.filesystem_paths = info.filesystem_paths; + + const result = await run(type ? `upload/${type}` : "upload", payload, { + title: "Uploading your files to DANDI", + }).catch((error) => { + this.notify(error.message, "error"); + throw error; + }); + + if (result) + this.notify( + `${ + info.project ?? `${info[folderPathKey].length} filesystem entries` + } successfully uploaded to Dandiset ${dandiset_id}`, + "success" + ); + + return result; +} + +export class UploadsPage extends Page { + header = { + title: "NWB File Uploads", + subtitle: "Upload folders and individual NWB files to the DANDI Archive.", + controls: [ + new Button({ + icon: keyIcon, + label: "API Keys", + onClick: () => { + this.#globalModal.form.results = structuredClone(global.data.DANDI?.api_keys ?? {}); + this.#globalModal.open = true; + }, + }), + ], + }; + + constructor(...args) { + super(...args); + } + + #globalModal = null; + + #saveNotification; + connectedCallback() { + super.connectedCallback(); + + const modal = (this.#globalModal = createFormModal.call(this, { + header: "DANDI API Keys", + schema: dandiGlobalSchema.properties.api_keys, + onSave: async (form) => { + if (this.#saveNotification) this.dismiss(this.#saveNotification); + + const apiKeys = form.resolved; + if (!Object.keys(apiKeys).length) { + this.#saveNotification = this.notify("No API keys were provided", "error"); + return null; + } + + const globalDandiData = global.data.DANDI ?? (global.data.DANDI = {}); + if (!globalDandiData.api_keys) globalDandiData.api_keys = {}; + merge(apiKeys, globalDandiData.api_keys); + + global.save(); + await regenerateDandisets(); + const input = this.form.getFormElement(["dandisets"]); + input.requestUpdate(); + }, + formProps: { + validateOnChange: async (name, parent) => { + const value = parent[name]; + if (name.includes("api_key")) return await validateDANDIApiKey(value, name.includes("staging")); + }, + }, + })); + document.body.append(modal); + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this.#globalModal) this.#globalModal.remove(); + } + + render() { + const globalState = (global.data.uploads = global.data.uploads ?? {}); + const defaultButtonMessage = "Upload Files"; + + const button = new Button({ + label: defaultButtonMessage, + onClick: async () => { + await this.form.validate(); // Will throw an error in the callback + + const results = await uploadToDandi.call(this, { ...global.data.uploads }); + global.data.uploads = {}; + global.save(); + + const modal = new Modal({ open: true }); + modal.header = "DANDI Upload Summary"; + const summary = new DandiResults({ + id: globalState.dandiset, + files: { + subject: results.map((file) => { + return { file }; + }), + }, + }); + summary.style.padding = "25px"; + modal.append(summary); + + document.body.append(modal); + + this.requestUpdate(); + }, + }); + + const promise = ready.cpus + .then(() => ready.dandisets) + .then(() => { + // NOTE: API Keys and Dandiset IDs persist across selected project + return (this.form = new JSONSchemaForm({ + results: globalState, + schema: dandiSchema, + validateEmptyValues: false, + controls: { + dandiset: [ + new Button({ + label: "Create New Dandiset", + buttonStyles: { + width: "max-content", + }, + onClick: async () => { + await createDandiset.call(this, { title: this.form.resolved.dandiset }); + this.requestUpdate(); + }, + }), + ], + }, + sort: ([k1]) => { + if (k1 === folderPathKey) return -1; + }, + onUpdate: ([id]) => { + if (id === folderPathKey) { + const keysToUpdate = ["dandiset"]; + keysToUpdate.forEach((k) => { + const input = this.form.getFormElement([k]); + if (input.value) input.updateData(""); + }); + } + + global.save(); + }, + + onThrow, + + transformErrors: (error) => { + if (error.message === "Filesystem Paths is a required property.") + error.message = "Please select at least one file or folder to upload."; + }, + + validateOnChange: validate, + })); + }) + .catch((error) => html`

${error}

`); + + // Confirm that one api key exists + promise.then(() => { + const api_keys = global.data.DANDI?.api_keys; + if (!api_keys || !Object.keys(api_keys).length) this.#globalModal.open = true; + }); + + return html` + ${until( + promise.then((form) => { + return html` + ${form} +
+ ${button} + `; + }), + html`

Loading form contents...

+

` + )} + `; + } +} + +customElements.get("nwbguide-uploads-page") || customElements.define("nwbguide-uploads-page", UploadsPage); diff --git a/stories/Pages.stories.js b/stories/Pages.stories.js index f72751a46..a697bbd9c 100644 --- a/stories/Pages.stories.js +++ b/stories/Pages.stories.js @@ -1,36 +1,36 @@ -import { dashboard } from "../src/electron/renderer/src/pages.js"; - -// const options = Object.keys(dashboard.pagesById) - -export default { - title: "Pages", - parameters: { - chromatic: { disableSnapshot: false }, - }, - // argTypes: { - // activePage: { - // options, - // control: { type: 'select' } - // }, - // }, -}; - -const Template = (args = {}) => { - for (let k in args) dashboard.setAttribute(k, args[k]); - return dashboard; -}; - -export const Overview = Template.bind({}); -Overview.args = { - activePage: "/", -}; - -export const Documentation = Template.bind({}); -Documentation.args = { - activePage: "docs", -}; - -export const Contact = Template.bind({}); -Contact.args = { - activePage: "contact", -}; +import { dashboard } from "../src/electron/renderer/src/pages.js"; + +// const options = Object.keys(dashboard.pagesById) + +export default { + title: "Pages", + parameters: { + chromatic: { disableSnapshot: false }, + }, + // argTypes: { + // activePage: { + // options, + // control: { type: 'select' } + // }, + // }, +}; + +const Template = (args = {}) => { + for (let k in args) dashboard.setAttribute(k, args[k]); + return dashboard; +}; + +export const Overview = Template.bind({}); +Overview.args = { + activePage: "/", +}; + +export const Documentation = Template.bind({}); +Documentation.args = { + activePage: "docs", +}; + +export const Contact = Template.bind({}); +Contact.args = { + activePage: "contact", +}; diff --git a/stories/components/Accordion.stories.js b/stories/components/Accordion.stories.js index 781198531..f6b1d2599 100644 --- a/stories/components/Accordion.stories.js +++ b/stories/components/Accordion.stories.js @@ -1,42 +1,42 @@ -import { Accordion } from "../../src/electron/renderer/src/stories/Accordion"; - -export default { - title: "Components/Accordion", -}; - -const Template = (args) => new Accordion(args); - -const base = { - name: "NWBFile", - open: true, - status: "valid", - content: "Whatever you want", - subtitle: "General NWB File Properties", -}; - -export const Basic = Template.bind({}); -Basic.args = { - ...base, - content: "Whatever you want", -}; - -export const Valid = Template.bind({}); -Valid.args = { - ...base, - status: "valid", - content: "WOOHOO!", -}; - -export const Warning = Template.bind({}); -Warning.args = { - ...base, - status: "warning", - content: "OOO", -}; - -export const Error = Template.bind({}); -Error.args = { - ...base, - status: "error", - content: "BOO", -}; +import { Accordion } from "../../src/electron/renderer/src/stories/Accordion"; + +export default { + title: "Components/Accordion", +}; + +const Template = (args) => new Accordion(args); + +const base = { + name: "NWBFile", + open: true, + status: "valid", + content: "Whatever you want", + subtitle: "General NWB File Properties", +}; + +export const Basic = Template.bind({}); +Basic.args = { + ...base, + content: "Whatever you want", +}; + +export const Valid = Template.bind({}); +Valid.args = { + ...base, + status: "valid", + content: "WOOHOO!", +}; + +export const Warning = Template.bind({}); +Warning.args = { + ...base, + status: "warning", + content: "OOO", +}; + +export const Error = Template.bind({}); +Error.args = { + ...base, + status: "error", + content: "BOO", +}; diff --git a/stories/components/Button.stories.js b/stories/components/Button.stories.js index eece1e312..9e91838d2 100644 --- a/stories/components/Button.stories.js +++ b/stories/components/Button.stories.js @@ -1,49 +1,49 @@ -import { Button } from "../../src/electron/renderer/src/stories/Button"; - -// More on how to set up stories at: https://storybook.js.org/docs/7.0/web-components/writing-stories/introduction -export default { - title: "Components/Button", - tags: ["autodocs"], - parameters: { - chromatic: { disableSnapshot: false }, - }, - argTypes: { - backgroundColor: { control: "color" }, - onClick: { action: "onClick" }, - size: { - control: { type: "select" }, - options: ["small", "medium", "large"], - }, - }, -}; - -const Template = (args) => new Button(args); - -export const Primary = Template.bind({}); - -const base = { - label: "Button", -}; - -Primary.args = { - ...base, - primary: true, -}; - -export const Secondary = Template.bind({}); - -Secondary.args = { ...base }; - -export const Large = Template.bind({}); - -Large.args = { - ...base, - size: "large", -}; - -export const Small = Template.bind({}); - -Small.args = { - ...base, - size: "small", -}; +import { Button } from "../../src/electron/renderer/src/stories/Button"; + +// More on how to set up stories at: https://storybook.js.org/docs/7.0/web-components/writing-stories/introduction +export default { + title: "Components/Button", + tags: ["autodocs"], + parameters: { + chromatic: { disableSnapshot: false }, + }, + argTypes: { + backgroundColor: { control: "color" }, + onClick: { action: "onClick" }, + size: { + control: { type: "select" }, + options: ["small", "medium", "large"], + }, + }, +}; + +const Template = (args) => new Button(args); + +export const Primary = Template.bind({}); + +const base = { + label: "Button", +}; + +Primary.args = { + ...base, + primary: true, +}; + +export const Secondary = Template.bind({}); + +Secondary.args = { ...base }; + +export const Large = Template.bind({}); + +Large.args = { + ...base, + size: "large", +}; + +export const Small = Template.bind({}); + +Small.args = { + ...base, + size: "small", +}; diff --git a/stories/components/FileSystemSelector.stories.js b/stories/components/FileSystemSelector.stories.js index 282598976..eac8a8998 100644 --- a/stories/components/FileSystemSelector.stories.js +++ b/stories/components/FileSystemSelector.stories.js @@ -1,35 +1,35 @@ -import { FilesystemSelector } from "../../src/electron/renderer/src/stories/FileSystemSelector"; - -export default { - title: "Components/Filesystem Selector", -}; - -const Template = (args) => new FilesystemSelector(args); - -const types = ["file", "directory"]; - -export const File = Template.bind({}); -export const Folder = Template.bind({}); -Folder.args = { - type: types[1], -}; - -export const FileMultiple = Template.bind({}); -FileMultiple.args = { multiple: true }; - -export const FolderMultiple = Template.bind({}); -FolderMultiple.args = { - type: types[1], - multiple: true, -}; - -export const Both = Template.bind({}); -Both.args = { - type: types, -}; - -export const BothMultiple = Template.bind({}); -BothMultiple.args = { - type: types, - multiple: true, -}; +import { FilesystemSelector } from "../../src/electron/renderer/src/stories/FileSystemSelector"; + +export default { + title: "Components/Filesystem Selector", +}; + +const Template = (args) => new FilesystemSelector(args); + +const types = ["file", "directory"]; + +export const File = Template.bind({}); +export const Folder = Template.bind({}); +Folder.args = { + type: types[1], +}; + +export const FileMultiple = Template.bind({}); +FileMultiple.args = { multiple: true }; + +export const FolderMultiple = Template.bind({}); +FolderMultiple.args = { + type: types[1], + multiple: true, +}; + +export const Both = Template.bind({}); +Both.args = { + type: types, +}; + +export const BothMultiple = Template.bind({}); +BothMultiple.args = { + type: types, + multiple: true, +}; diff --git a/stories/components/InspectorList.stories.js b/stories/components/InspectorList.stories.js index 16a2b91da..43c41ae7c 100644 --- a/stories/components/InspectorList.stories.js +++ b/stories/components/InspectorList.stories.js @@ -1,13 +1,13 @@ -import { InspectorList } from "../../src/electron/renderer/src/stories/preview/inspector/InspectorList"; -import testInspectorList from "../data/inspector_output.json"; - -export default { - title: "Components/Inspector List", -}; - -const Template = (args) => new InspectorList(args); - -export const Default = Template.bind({}); -Default.args = { - items: testInspectorList, -}; +import { InspectorList } from "../../src/electron/renderer/src/stories/preview/inspector/InspectorList"; +import testInspectorList from "../data/inspector_output.json"; + +export default { + title: "Components/Inspector List", +}; + +const Template = (args) => new InspectorList(args); + +export const Default = Template.bind({}); +Default.args = { + items: testInspectorList, +}; diff --git a/stories/components/InstanceManager.stories.js b/stories/components/InstanceManager.stories.js index 098fd55be..867fd1e1c 100644 --- a/stories/components/InstanceManager.stories.js +++ b/stories/components/InstanceManager.stories.js @@ -1,81 +1,81 @@ -import { InstanceManager } from "../../src/electron/renderer/src/stories/InstanceManager"; -import { Modal } from "../../src/electron/renderer/src/stories/Modal"; - -// More on how to set up stories at: https://storybook.js.org/docs/7.0/web-components/writing-stories/introduction -export default { - title: "Components/Instance Manager", - parameters: { - chromatic: { disableSnapshot: false }, - }, -}; - -const Template = (args) => new InstanceManager(args); - -export const Primary = Template.bind({}); - -const instances = { - thisisalongidentifier: { - content: "This has no status", - }, - thisisanevenlongeridentifierwithincorrectinfo: { - content: "This is very wrong", - status: "error", - }, - "002": { - content: "This is kinda right", - status: "warning", - }, - "003": { - content: "This is done", - status: "valid", - }, - category: { - item: { - content: "This is nested", - status: "valid", - }, - }, -}; - -const convertToElement = (_, object) => { - const div = document.createElement("div"); - div.innerHTML = `${JSON.stringify(object)}`; - return div; -}; - -const drillObjects = (object) => { - const keys = Object.keys(object); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const value = object[key]; - if (typeof value === "object") drillObjects(value); - else object[key] = convertToElement(key, value); - } - return object; -}; - -Primary.args = { - header: "Subject One", - controls: [ - { - name: "Preview", - onClick: function (key, contentElement) { - const parent = contentElement.parentNode; - const modal = new Modal({ - header: "Preview", - open: true, - onClose: () => { - modal.remove(); - parent.append(contentElement); - }, - }); - modal.append(contentElement); - document.body.appendChild(modal); - }, - }, - ], - instances, - onAdded: (path) => { - return convertToElement(path.join("/"), `This is some text for a new key: ${path.join("/")}`); - }, -}; +import { InstanceManager } from "../../src/electron/renderer/src/stories/InstanceManager"; +import { Modal } from "../../src/electron/renderer/src/stories/Modal"; + +// More on how to set up stories at: https://storybook.js.org/docs/7.0/web-components/writing-stories/introduction +export default { + title: "Components/Instance Manager", + parameters: { + chromatic: { disableSnapshot: false }, + }, +}; + +const Template = (args) => new InstanceManager(args); + +export const Primary = Template.bind({}); + +const instances = { + thisisalongidentifier: { + content: "This has no status", + }, + thisisanevenlongeridentifierwithincorrectinfo: { + content: "This is very wrong", + status: "error", + }, + "002": { + content: "This is kinda right", + status: "warning", + }, + "003": { + content: "This is done", + status: "valid", + }, + category: { + item: { + content: "This is nested", + status: "valid", + }, + }, +}; + +const convertToElement = (_, object) => { + const div = document.createElement("div"); + div.innerHTML = `${JSON.stringify(object)}`; + return div; +}; + +const drillObjects = (object) => { + const keys = Object.keys(object); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = object[key]; + if (typeof value === "object") drillObjects(value); + else object[key] = convertToElement(key, value); + } + return object; +}; + +Primary.args = { + header: "Subject One", + controls: [ + { + name: "Preview", + onClick: function (key, contentElement) { + const parent = contentElement.parentNode; + const modal = new Modal({ + header: "Preview", + open: true, + onClose: () => { + modal.remove(); + parent.append(contentElement); + }, + }); + modal.append(contentElement); + document.body.appendChild(modal); + }, + }, + ], + instances, + onAdded: (path) => { + return convertToElement(path.join("/"), `This is some text for a new key: ${path.join("/")}`); + }, +}; diff --git a/stories/components/JSONSchemaForm.stories.js b/stories/components/JSONSchemaForm.stories.js index 3c038f0fa..aa2932444 100644 --- a/stories/components/JSONSchemaForm.stories.js +++ b/stories/components/JSONSchemaForm.stories.js @@ -1,128 +1,128 @@ -import { JSONSchemaForm } from "../../src/electron/renderer/src/stories/JSONSchemaForm"; - -export default { - title: "Components/JSON Schema Form", - // Set controls - argTypes: {}, -}; - -const Template = (args) => new JSONSchemaForm(args); - -const defaultSchema = { - title: "Test Title", - description: "This is a test description", - properties: { - test: { - type: "string", - default: true, - description: "This is a test description", - }, - warn: { - type: "string", - }, - optional: { - type: "string", - format: "file", - }, - list: { - type: "array", - items: { - type: "string", - format: "file", - }, - }, - }, - required: ["test"], -}; - -export const Default = Template.bind({}); -Default.args = { - results: { - test: "false", - list: ["item"], - }, - schema: defaultSchema, -}; - -export const Nested = Template.bind({}); -Nested.args = { - results: { - name: "name", - ignored: true, - }, - schema: { - properties: { - name: { - type: "string", - }, - warn: { - type: "string", - }, - nested: defaultSchema, - }, - }, - required: { - name: true, - }, - - validateOnChange: (name, parentInfo, path) => { - if (name === "name" && parentInfo[name] !== "name") - return [ - { - type: "error", - message: 'Name must be "name"', - }, - ]; - - if (name === "warn" && parentInfo[name] !== "warn") - return [ - { - type: "warning", - message: 'Warn must be "warn"', - }, - ]; - }, -}; - -const linked = ["age", "date_of_birth"]; -export const Linked = Template.bind({}); -Linked.args = { - schema: { - properties: { - name: { - type: "string", - }, - required: { - type: "string", - }, - age: { - type: "number", - }, - date_of_birth: { - type: "string", - format: "date-time", - }, - }, - required: ["required"], - }, - groups: [ - { - name: "Subject Age", - properties: [["age"], ["date_of_birth"]], - }, - ], - validateOnChange: (name, parentInfo) => { - const bothUnspecified = !parentInfo["age"] && !parentInfo["date_of_birth"]; - - if (bothUnspecified && linked.includes(name)) - return [ - { - type: "error", - message: "Age or date of birth must be specified", - }, - ]; - }, -}; - -// ...this.info.globalState.metadata, -// validateOnChange +import { JSONSchemaForm } from "../../src/electron/renderer/src/stories/JSONSchemaForm"; + +export default { + title: "Components/JSON Schema Form", + // Set controls + argTypes: {}, +}; + +const Template = (args) => new JSONSchemaForm(args); + +const defaultSchema = { + title: "Test Title", + description: "This is a test description", + properties: { + test: { + type: "string", + default: true, + description: "This is a test description", + }, + warn: { + type: "string", + }, + optional: { + type: "string", + format: "file", + }, + list: { + type: "array", + items: { + type: "string", + format: "file", + }, + }, + }, + required: ["test"], +}; + +export const Default = Template.bind({}); +Default.args = { + results: { + test: "false", + list: ["item"], + }, + schema: defaultSchema, +}; + +export const Nested = Template.bind({}); +Nested.args = { + results: { + name: "name", + ignored: true, + }, + schema: { + properties: { + name: { + type: "string", + }, + warn: { + type: "string", + }, + nested: defaultSchema, + }, + }, + required: { + name: true, + }, + + validateOnChange: (name, parentInfo, path) => { + if (name === "name" && parentInfo[name] !== "name") + return [ + { + type: "error", + message: 'Name must be "name"', + }, + ]; + + if (name === "warn" && parentInfo[name] !== "warn") + return [ + { + type: "warning", + message: 'Warn must be "warn"', + }, + ]; + }, +}; + +const linked = ["age", "date_of_birth"]; +export const Linked = Template.bind({}); +Linked.args = { + schema: { + properties: { + name: { + type: "string", + }, + required: { + type: "string", + }, + age: { + type: "number", + }, + date_of_birth: { + type: "string", + format: "date-time", + }, + }, + required: ["required"], + }, + groups: [ + { + name: "Subject Age", + properties: [["age"], ["date_of_birth"]], + }, + ], + validateOnChange: (name, parentInfo) => { + const bothUnspecified = !parentInfo["age"] && !parentInfo["date_of_birth"]; + + if (bothUnspecified && linked.includes(name)) + return [ + { + type: "error", + message: "Age or date of birth must be specified", + }, + ]; + }, +}; + +// ...this.info.globalState.metadata, +// validateOnChange diff --git a/stories/components/List.stories.js b/stories/components/List.stories.js index a40a097d6..29338cb4d 100644 --- a/stories/components/List.stories.js +++ b/stories/components/List.stories.js @@ -1,32 +1,32 @@ -import { List } from "../../src/electron/renderer/src/stories/List"; - -export default { - title: "Components/List", -}; - -const Template = (args) => new List(args); - -const generateString = () => Math.floor(Math.random() * Date.now()).toString(36); - -export const Default = Template.bind({}); -Default.args = { - items: [{ value: "test" }, { value: Array.from({ length: 1000 }).map(generateString).join("") }], -}; - -export const WithKeys = Template.bind({}); -WithKeys.args = { - items: [{ key: "TestKey", value: "test" }], -}; - -export const Empty = Template.bind({}); -Empty.args = { - emptyMessage: "This is empty", - unordered: true, - items: [], -}; - -export const EmptyKeys = Template.bind({}); -EmptyKeys.args = { - emptyMessage: "This is empty", - items: [], -}; +import { List } from "../../src/electron/renderer/src/stories/List"; + +export default { + title: "Components/List", +}; + +const Template = (args) => new List(args); + +const generateString = () => Math.floor(Math.random() * Date.now()).toString(36); + +export const Default = Template.bind({}); +Default.args = { + items: [{ value: "test" }, { value: Array.from({ length: 1000 }).map(generateString).join("") }], +}; + +export const WithKeys = Template.bind({}); +WithKeys.args = { + items: [{ key: "TestKey", value: "test" }], +}; + +export const Empty = Template.bind({}); +Empty.args = { + emptyMessage: "This is empty", + unordered: true, + items: [], +}; + +export const EmptyKeys = Template.bind({}); +EmptyKeys.args = { + emptyMessage: "This is empty", + items: [], +}; diff --git a/stories/components/Locate.stories.js b/stories/components/Locate.stories.js index d86f73569..cd99bd836 100644 --- a/stories/components/Locate.stories.js +++ b/stories/components/Locate.stories.js @@ -1,29 +1,29 @@ -import { globalState, PageTemplate } from "../pages/storyStates"; - -export default { - title: "Pages/Guided Mode/Locate", - parameters: { - chromatic: { disableSnapshot: false }, - }, -}; - -export const Invalid = PageTemplate.bind({}); -Invalid.args = { - activePage: "//locate", - globalState, -}; - -export const Valid = PageTemplate.bind({}); - -const validGlobalState = structuredClone(globalState); -validGlobalState.structure.results = { - interface: { - base_directory: "/Users/garrettflynn/NWB_GUIDE/tutorial/Dataset", - format_string_path: "{subject_id}/{subject_id}_{session_id}/{subject_id}_{session_id}_phy", - }, -}; - -Valid.args = { - activePage: "//locate", - globalState: validGlobalState, -}; +import { globalState, PageTemplate } from "../pages/storyStates"; + +export default { + title: "Pages/Guided Mode/Locate", + parameters: { + chromatic: { disableSnapshot: false }, + }, +}; + +export const Invalid = PageTemplate.bind({}); +Invalid.args = { + activePage: "//locate", + globalState, +}; + +export const Valid = PageTemplate.bind({}); + +const validGlobalState = structuredClone(globalState); +validGlobalState.structure.results = { + interface: { + base_directory: "/Users/garrettflynn/NWB_GUIDE/tutorial/Dataset", + format_string_path: "{subject_id}/{subject_id}_{session_id}/{subject_id}_{session_id}_phy", + }, +}; + +Valid.args = { + activePage: "//locate", + globalState: validGlobalState, +}; diff --git a/stories/components/Multiselect.stories.js b/stories/components/Multiselect.stories.js index 083afab17..c40ab2d2f 100644 --- a/stories/components/Multiselect.stories.js +++ b/stories/components/Multiselect.stories.js @@ -1,34 +1,34 @@ -import { MultiSelectForm } from "../../src/electron/renderer/src/stories/multiselect/MultiSelectForm.js"; - -export default { - title: "Components/Multiselect Form", - parameters: { - chromatic: { disableSnapshot: false }, - }, -}; - -const Template = (args) => new MultiSelectForm(args); - -export const Default = Template.bind({}); -Default.args = { - header: "Test Header", - options: { - option1: { - name: "Option 1", - modality: "Modality 1", - technique: "Technique 1", - }, - - option2: { - name: "Option 2", - modality: "Modality 1", - technique: "Technique 1", - }, - - otheroption1: { - name: "Other Option 1", - modality: "Modality 2", - technique: "Technique 1", - }, - }, -}; +import { MultiSelectForm } from "../../src/electron/renderer/src/stories/multiselect/MultiSelectForm.js"; + +export default { + title: "Components/Multiselect Form", + parameters: { + chromatic: { disableSnapshot: false }, + }, +}; + +const Template = (args) => new MultiSelectForm(args); + +export const Default = Template.bind({}); +Default.args = { + header: "Test Header", + options: { + option1: { + name: "Option 1", + modality: "Modality 1", + technique: "Technique 1", + }, + + option2: { + name: "Option 2", + modality: "Modality 1", + technique: "Technique 1", + }, + + otheroption1: { + name: "Other Option 1", + modality: "Modality 2", + technique: "Technique 1", + }, + }, +}; diff --git a/stories/components/OptionalSection.stories.js b/stories/components/OptionalSection.stories.js index f0c3751d4..b4c3c24eb 100644 --- a/stories/components/OptionalSection.stories.js +++ b/stories/components/OptionalSection.stories.js @@ -1,15 +1,15 @@ -import { OptionalSection } from "../../src/electron/renderer/src/stories/OptionalSection"; - -export default { - title: "Components/Optional Section", - parameters: { - chromatic: { disableSnapshot: false }, - }, -}; - -const Template = (args) => new OptionalSection(args); - -export const Default = Template.bind({}); -Default.args = { - content: "This is the content of the optional section.", -}; +import { OptionalSection } from "../../src/electron/renderer/src/stories/OptionalSection"; + +export default { + title: "Components/Optional Section", + parameters: { + chromatic: { disableSnapshot: false }, + }, +}; + +const Template = (args) => new OptionalSection(args); + +export const Default = Template.bind({}); +Default.args = { + content: "This is the content of the optional section.", +}; diff --git a/stories/components/ProgressBar.stories.js b/stories/components/ProgressBar.stories.js index f5b9ab929..c8acf7ea3 100644 --- a/stories/components/ProgressBar.stories.js +++ b/stories/components/ProgressBar.stories.js @@ -1,30 +1,30 @@ -import { ProgressBar } from "../../src/electron/renderer/src/stories/ProgressBar"; - -export default { - title: "Components/Progress Bar", -}; - -const Template = (args) => new ProgressBar(args); - -const n = 0; -const total = 50; - -export const Default = Template.bind({}); -Default.args = { - format: { - n, - total, - }, -}; - -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -Default.play = async ({ canvasElement }) => { - const progressBar = canvasElement.querySelector("nwb-progress"); - for (let i = 1; i <= total; i++) { - await sleep(1000); - progressBar.format = { n: i, total }; - } -}; +import { ProgressBar } from "../../src/electron/renderer/src/stories/ProgressBar"; + +export default { + title: "Components/Progress Bar", +}; + +const Template = (args) => new ProgressBar(args); + +const n = 0; +const total = 50; + +export const Default = Template.bind({}); +Default.args = { + format: { + n, + total, + }, +}; + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +Default.play = async ({ canvasElement }) => { + const progressBar = canvasElement.querySelector("nwb-progress"); + for (let i = 1; i <= total; i++) { + await sleep(1000); + progressBar.format = { n: i, total }; + } +}; diff --git a/stories/components/Search.stories.js b/stories/components/Search.stories.js index a5c200017..ff6ad16a4 100644 --- a/stories/components/Search.stories.js +++ b/stories/components/Search.stories.js @@ -1,46 +1,46 @@ -import { Search } from "../../src/electron/renderer/src/stories/Search"; - -// More on how to set up stories at: https://storybook.js.org/docs/7.0/web-components/writing-stories/introduction -export default { - title: "Components/Search", - // tags: ['autodocs'], -}; - -const Template = (args) => new Search(args); - -export const Default = Template.bind({}); -Default.args = { - disabledLabel: "Interface not supported", - options: [ - { - label: "SpikeGLXRecording", - keywords: ["extracellular electrophysiology", "voltage", "recording", "neuropixels"], - }, - { - label: "DeepLabCut", - keywords: ["DLC", "tracking", "pose estimation"], - }, - ], -}; - -export const Categories = Template.bind({}); -Categories.args = { - disabledLabel: "Interface not supported", - options: [ - { - label: "SpikeGLXConverter", - category: "Converter", - keywords: ["extracellular electrophysiology", "voltage", "recording", "neuropixels"], - }, - { - label: "SpikeGLXRecording", - category: "Interface", - keywords: ["extracellular electrophysiology", "voltage", "recording", "neuropixels"], - }, - { - label: "DeepLabCut", - category: "Interface", - keywords: ["DLC", "tracking", "pose estimation"], - }, - ], -}; +import { Search } from "../../src/electron/renderer/src/stories/Search"; + +// More on how to set up stories at: https://storybook.js.org/docs/7.0/web-components/writing-stories/introduction +export default { + title: "Components/Search", + // tags: ['autodocs'], +}; + +const Template = (args) => new Search(args); + +export const Default = Template.bind({}); +Default.args = { + disabledLabel: "Interface not supported", + options: [ + { + label: "SpikeGLXRecording", + keywords: ["extracellular electrophysiology", "voltage", "recording", "neuropixels"], + }, + { + label: "DeepLabCut", + keywords: ["DLC", "tracking", "pose estimation"], + }, + ], +}; + +export const Categories = Template.bind({}); +Categories.args = { + disabledLabel: "Interface not supported", + options: [ + { + label: "SpikeGLXConverter", + category: "Converter", + keywords: ["extracellular electrophysiology", "voltage", "recording", "neuropixels"], + }, + { + label: "SpikeGLXRecording", + category: "Interface", + keywords: ["extracellular electrophysiology", "voltage", "recording", "neuropixels"], + }, + { + label: "DeepLabCut", + category: "Interface", + keywords: ["DLC", "tracking", "pose estimation"], + }, + ], +}; diff --git a/stories/components/StatusBar.stories.js b/stories/components/StatusBar.stories.js index 342325e29..6666cca4a 100644 --- a/stories/components/StatusBar.stories.js +++ b/stories/components/StatusBar.stories.js @@ -1,32 +1,32 @@ -import { StatusBar } from "../../src/electron/renderer/src/stories/status/StatusBar"; -import { unsafeSVG } from "lit/directives/unsafe-svg.js"; -import pythonSVG from "../../src/electron/renderer/assets/icons/python.svg?raw"; -import webAssetSVG from "../../src/electron/renderer/assets/icons/web_asset.svg?raw"; -import wifiSVG from "../../src/electron/renderer/assets/icons/wifi.svg?raw"; - -export default { - title: "Components/Status Bar", -}; - -const Template = (args) => new StatusBar(args); - -export const Default = Template.bind({}); -Default.args = { - items: [ - { label: unsafeSVG(webAssetSVG), value: "0.0.3" }, - { label: unsafeSVG(wifiSVG) }, - { label: unsafeSVG(pythonSVG), status: true }, - { label: "Other", status: false }, - ], -}; - -// { -// pages: { -// 'Page 1': { -// active: true -// }, -// 'Page 2': { -// active: false -// }, -// } -// } +import { StatusBar } from "../../src/electron/renderer/src/stories/status/StatusBar"; +import { unsafeSVG } from "lit/directives/unsafe-svg.js"; +import pythonSVG from "../../src/electron/renderer/assets/icons/python.svg?raw"; +import webAssetSVG from "../../src/electron/renderer/assets/icons/web_asset.svg?raw"; +import wifiSVG from "../../src/electron/renderer/assets/icons/wifi.svg?raw"; + +export default { + title: "Components/Status Bar", +}; + +const Template = (args) => new StatusBar(args); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { label: unsafeSVG(webAssetSVG), value: "0.0.3" }, + { label: unsafeSVG(wifiSVG) }, + { label: unsafeSVG(pythonSVG), status: true }, + { label: "Other", status: false }, + ], +}; + +// { +// pages: { +// 'Page 1': { +// active: true +// }, +// 'Page 2': { +// active: false +// }, +// } +// } diff --git a/stories/components/Table.stories.js b/stories/components/Table.stories.js index e4272dee4..4d9cb88c0 100644 --- a/stories/components/Table.stories.js +++ b/stories/components/Table.stories.js @@ -1,68 +1,68 @@ -import { Table } from "../../src/electron/renderer/src/stories/Table.js"; - -import getSubjectSchema from "../../src/schemas/subject.schema"; -import { SimpleTable } from "../../src/electron/renderer/src/stories/SimpleTable.js"; -import { BasicTable } from "../../src/electron/renderer/src/stories/BasicTable.js"; - -export default { - title: "Components/Table", - parameters: { - chromatic: { disableSnapshot: true }, - }, -}; - -const Template = (args) => new Table(args); - -const subjects = 100; -const subjectIds = Array.from({ length: subjects }, (_, i) => i); - -const data = subjectIds.reduce((acc, key) => { - acc[key] = { - weight: (60 * Math.random()).toFixed(2), - sessions: [1], - }; - return acc; -}, {}); - -const BasicTableTemplate = (args) => new BasicTable(args); - -const subjectSchema = getSubjectSchema(); - -subjectSchema.additionalProperties = true; - -const subjectTableSchema = { - type: "array", - items: subjectSchema, -}; - -export const Basic = BasicTableTemplate.bind({}); -Basic.args = { - name: "basic_table_test", - schema: subjectTableSchema, - data, - keyColumn: "subject_id", - validateOnChange: (key, parent, value) => !!value, // Always validate as true -}; - -export const Default = Template.bind({}); -Default.args = { - schema: subjectTableSchema, - data, - keyColumn: "subject_id", - validateOnChange: () => true, // Always validate as true -}; - -const SimpleTemplate = (args) => new SimpleTable(args); - -export const Simple = SimpleTemplate.bind({}); -Simple.args = { - schema: subjectTableSchema, - data, - keyColumn: "subject_id", - validateOnChange: (key, parent, value) => { - return !!value; - }, // Always validate as true - onLoaded: () => { - console.log("Loaded!"); - }, -}; +import { Table } from "../../src/electron/renderer/src/stories/Table.js"; + +import getSubjectSchema from "../../src/schemas/subject.schema"; +import { SimpleTable } from "../../src/electron/renderer/src/stories/SimpleTable.js"; +import { BasicTable } from "../../src/electron/renderer/src/stories/BasicTable.js"; + +export default { + title: "Components/Table", + parameters: { + chromatic: { disableSnapshot: true }, + }, +}; + +const Template = (args) => new Table(args); + +const subjects = 100; +const subjectIds = Array.from({ length: subjects }, (_, i) => i); + +const data = subjectIds.reduce((acc, key) => { + acc[key] = { + weight: (60 * Math.random()).toFixed(2), + sessions: [1], + }; + return acc; +}, {}); + +const BasicTableTemplate = (args) => new BasicTable(args); + +const subjectSchema = getSubjectSchema(); + +subjectSchema.additionalProperties = true; + +const subjectTableSchema = { + type: "array", + items: subjectSchema, +}; + +export const Basic = BasicTableTemplate.bind({}); +Basic.args = { + name: "basic_table_test", + schema: subjectTableSchema, + data, + keyColumn: "subject_id", + validateOnChange: (key, parent, value) => !!value, // Always validate as true +}; + +export const Default = Template.bind({}); +Default.args = { + schema: subjectTableSchema, + data, + keyColumn: "subject_id", + validateOnChange: () => true, // Always validate as true +}; + +const SimpleTemplate = (args) => new SimpleTable(args); + +export const Simple = SimpleTemplate.bind({}); +Simple.args = { + schema: subjectTableSchema, + data, + keyColumn: "subject_id", + validateOnChange: (key, parent, value) => { + return !!value; + }, // Always validate as true + onLoaded: () => { + console.log("Loaded!"); + }, +}; diff --git a/stories/pages/SourceData.stories.js b/stories/pages/SourceData.stories.js index dc4daf3e3..841007cf2 100644 --- a/stories/pages/SourceData.stories.js +++ b/stories/pages/SourceData.stories.js @@ -1,426 +1,426 @@ -import { globalState, PageTemplate } from "./storyStates"; -import SpikeGLXRecordingInterfaceSchema from "../inputs/interface_schemas/SpikeGLXRecordingInterface.json"; -import SpikeGLXNIDQInterfaceSchema from "../inputs/interface_schemas/SpikeGLXNIDQInterface.json"; -import PhySortingInterfaceSchema from "../inputs/interface_schemas/PhySortingInterface.json"; -import NeuroScopeRecordingInterfaceSchema from "../inputs/interface_schemas/NeuroScopeRecordingInterface.json"; -import NeuroScopeLFPInterfaceSchema from "../inputs/interface_schemas/NeuroScopeLFPInterface.json"; -import NeuroScopeSortingInterfaceSchema from "../inputs/interface_schemas/NeuroScopeSortingInterface.json"; -import BiocamRecordingInterfaceSchema from "../inputs/interface_schemas/BiocamRecordingInterface.json"; -import IntanRecordingInterfaceSchema from "../inputs/interface_schemas/IntanRecordingInterface.json"; -import OpenEphysRecordingInterfaceSchema from "../inputs/interface_schemas/OpenEphysRecordingInterface.json"; -import BlackrockRecordingInterfaceSchema from "../inputs/interface_schemas/BlackrockRecordingInterface.json"; -import BlackrockSortingInterfaceSchema from "../inputs/interface_schemas/BlackrockSortingInterface.json"; -import CellExplorerSortingInterfaceSchema from "../inputs/interface_schemas/CellExplorerSortingInterface.json"; -import KiloSortSortingInterfaceSchema from "../inputs/interface_schemas/KiloSortSortingInterface.json"; -import TdtRecordingInterfaceSchema from "../inputs/interface_schemas/TdtRecordingInterface.json"; -import Spike2RecordingInterfaceSchema from "../inputs/interface_schemas/Spike2RecordingInterface.json"; -import BrukerTiffSinglePlaneImagingInterfaceSchema from "../inputs/interface_schemas/BrukerTiffSinglePlaneImagingInterface.json"; -import ExtractSegmentationInterfaceSchema from "../inputs/interface_schemas/ExtractSegmentationInterface.json"; -import CnmfeSegmentationInterfaceSchema from "../inputs/interface_schemas/CnmfeSegmentationInterface.json"; -import BrukerTiffMultiPlaneImagingInterfaceSchema from "../inputs/interface_schemas/BrukerTiffMultiPlaneImagingInterface.json"; -import MicroManagerTiffImagingInterfaceSchema from "../inputs/interface_schemas/MicroManagerTiffImagingInterface.json"; -import ScanImageImagingInterfaceSchema from "../inputs/interface_schemas/ScanImageImagingInterface.json"; -import TiffImagingInterfaceSchema from "../inputs/interface_schemas/TiffImagingInterface.json"; -import MiniscopeImagingInterfaceSchema from "../inputs/interface_schemas/MiniscopeImagingInterface.json"; -import SbxImagingInterfaceSchema from "../inputs/interface_schemas/SbxImagingInterface.json"; -import CaimanSegmentationInterfaceSchema from "../inputs/interface_schemas/CaimanSegmentationInterface.json"; -import MCSRawRecordingInterfaceSchema from "../inputs/interface_schemas/MCSRawRecordingInterface.json"; -import MEArecRecordingInterfaceSchema from "../inputs/interface_schemas/MEArecRecordingInterface.json"; -import PlexonRecordingInterfaceSchema from "../inputs/interface_schemas/PlexonRecordingInterface.json"; -import PlexonSortingInterfaceSchema from "../inputs/interface_schemas/PlexonSortingInterface.json"; -import AxonaRecordingInterfaceSchema from "../inputs/interface_schemas/AxonaRecordingInterface.json"; -import VideoInterfaceSchema from "../inputs/interface_schemas/VideoInterface.json"; -import NeuralynxRecordingInterfaceSchema from "../inputs/interface_schemas/NeuralynxRecordingInterface.json"; -import Suite2pSegmentationInterfaceSchema from "../inputs/interface_schemas/Suite2pSegmentationInterface.json"; -import AlphaOmegaRecordingInterfaceSchema from "../inputs/interface_schemas/AlphaOmegaRecordingInterface.json"; -import DeepLabCutInterfaceSchema from "../inputs/interface_schemas/DeepLabCutInterface.json"; -import SLEAPInterfaceSchema from "../inputs/interface_schemas/SLEAPInterface.json"; -import FicTracDataInterfaceSchema from "../inputs/interface_schemas/FicTracDataInterface.json"; -import AudioInterfaceSchema from "../inputs/interface_schemas/AudioInterface.json"; -import MiniscopeBehaviorInterfaceSchema from "../inputs/interface_schemas/MiniscopeBehaviorInterface.json"; -import EDFRecordingInterfaceSchema from "../inputs/interface_schemas/EDFRecordingInterface.json"; -import SpikeGLXConverterPipeSchema from "../inputs/interface_schemas/SpikeGLXConverterPipe.json"; -import BrukerTiffSinglePlaneConverterSchema from "../inputs/interface_schemas/BrukerTiffSinglePlaneConverter.json"; -import BrukerTiffMultiPlaneConverterSchema from "../inputs/interface_schemas/BrukerTiffMultiPlaneConverter.json"; -import MiniscopeConverterSchema from "../inputs/interface_schemas/MiniscopeConverter.json"; -import CellExplorerRecordingInterfaceSchema from "../inputs/interface_schemas/CellExplorerRecordingInterface.json"; - -export default { - title: "Pages/Guided Mode/Source Data", - parameters: { - chromatic: { disableSnapshot: false }, - }, -}; - -const activePage = "//sourcedata"; - -const globalStateCopy = JSON.parse(JSON.stringify(globalState)); -globalStateCopy.schema.source_data.properties.SpikeGLXRecordingInterface = - SpikeGLXRecordingInterfaceSchema.properties.SpikeGLXRecordingInterface; -globalStateCopy.schema.source_data.properties.SpikeGLXNIDQInterface = - SpikeGLXNIDQInterfaceSchema.properties.SpikeGLXNIDQInterface; -globalStateCopy.schema.source_data.properties.PhySortingInterface = - PhySortingInterfaceSchema.properties.PhySortingInterface; -globalStateCopy.schema.source_data.properties.NeuroScopeRecordingInterface = - NeuroScopeRecordingInterfaceSchema.properties.NeuroScopeRecordingInterface; -globalStateCopy.schema.source_data.properties.NeuroScopeLFPInterface = - NeuroScopeLFPInterfaceSchema.properties.NeuroScopeLFPInterface; -globalStateCopy.schema.source_data.properties.NeuroScopeSortingInterface = - NeuroScopeSortingInterfaceSchema.properties.NeuroScopeSortingInterface; -globalStateCopy.schema.source_data.properties.BiocamRecordingInterface = - BiocamRecordingInterfaceSchema.properties.BiocamRecordingInterface; -globalStateCopy.schema.source_data.properties.IntanRecordingInterface = - IntanRecordingInterfaceSchema.properties.IntanRecordingInterface; -globalStateCopy.schema.source_data.properties.OpenEphysRecordingInterface = - OpenEphysRecordingInterfaceSchema.properties.OpenEphysRecordingInterface; -globalStateCopy.schema.source_data.properties.BlackrockRecordingInterface = - BlackrockRecordingInterfaceSchema.properties.BlackrockRecordingInterface; -globalStateCopy.schema.source_data.properties.BlackrockSortingInterface = - BlackrockSortingInterfaceSchema.properties.BlackrockSortingInterface; -globalStateCopy.schema.source_data.properties.CellExplorerSortingInterface = - CellExplorerSortingInterfaceSchema.properties.CellExplorerSortingInterface; -globalStateCopy.schema.source_data.properties.KiloSortSortingInterface = - KiloSortSortingInterfaceSchema.properties.KiloSortSortingInterface; -globalStateCopy.schema.source_data.properties.TdtRecordingInterface = - TdtRecordingInterfaceSchema.properties.TdtRecordingInterface; -globalStateCopy.schema.source_data.properties.Spike2RecordingInterface = - Spike2RecordingInterfaceSchema.properties.Spike2RecordingInterface; -globalStateCopy.schema.source_data.properties.BrukerTiffSinglePlaneImagingInterface = - BrukerTiffSinglePlaneImagingInterfaceSchema.properties.BrukerTiffSinglePlaneImagingInterface; -globalStateCopy.schema.source_data.properties.ExtractSegmentationInterface = - ExtractSegmentationInterfaceSchema.properties.ExtractSegmentationInterface; -globalStateCopy.schema.source_data.properties.CnmfeSegmentationInterface = - CnmfeSegmentationInterfaceSchema.properties.CnmfeSegmentationInterface; -globalStateCopy.schema.source_data.properties.BrukerTiffMultiPlaneImagingInterface = - BrukerTiffMultiPlaneImagingInterfaceSchema.properties.BrukerTiffMultiPlaneImagingInterface; -globalStateCopy.schema.source_data.properties.MicroManagerTiffImagingInterface = - MicroManagerTiffImagingInterfaceSchema.properties.MicroManagerTiffImagingInterface; -globalStateCopy.schema.source_data.properties.ScanImageImagingInterface = - ScanImageImagingInterfaceSchema.properties.ScanImageImagingInterface; -globalStateCopy.schema.source_data.properties.TiffImagingInterface = - TiffImagingInterfaceSchema.properties.TiffImagingInterface; -globalStateCopy.schema.source_data.properties.MiniscopeImagingInterface = - MiniscopeImagingInterfaceSchema.properties.MiniscopeImagingInterface; -globalStateCopy.schema.source_data.properties.SbxImagingInterface = - SbxImagingInterfaceSchema.properties.SbxImagingInterface; -globalStateCopy.schema.source_data.properties.CaimanSegmentationInterface = - CaimanSegmentationInterfaceSchema.properties.CaimanSegmentationInterface; -globalStateCopy.schema.source_data.properties.MCSRawRecordingInterface = - MCSRawRecordingInterfaceSchema.properties.MCSRawRecordingInterface; -globalStateCopy.schema.source_data.properties.MEArecRecordingInterface = - MEArecRecordingInterfaceSchema.properties.MEArecRecordingInterface; -globalStateCopy.schema.source_data.properties.PlexonRecordingInterface = - PlexonRecordingInterfaceSchema.properties.PlexonRecordingInterface; -globalStateCopy.schema.source_data.properties.PlexonSortingInterface = - PlexonSortingInterfaceSchema.properties.PlexonSortingInterface; -globalStateCopy.schema.source_data.properties.AxonaRecordingInterface = - AxonaRecordingInterfaceSchema.properties.AxonaRecordingInterface; -globalStateCopy.schema.source_data.properties.VideoInterface = VideoInterfaceSchema.properties.VideoInterface; -globalStateCopy.schema.source_data.properties.NeuralynxRecordingInterface = - NeuralynxRecordingInterfaceSchema.properties.NeuralynxRecordingInterface; -globalStateCopy.schema.source_data.properties.Suite2pSegmentationInterface = - Suite2pSegmentationInterfaceSchema.properties.Suite2pSegmentationInterface; -globalStateCopy.schema.source_data.properties.AlphaOmegaRecordingInterface = - AlphaOmegaRecordingInterfaceSchema.properties.AlphaOmegaRecordingInterface; -globalStateCopy.schema.source_data.properties.DeepLabCutInterface = - DeepLabCutInterfaceSchema.properties.DeepLabCutInterface; -globalStateCopy.schema.source_data.properties.SLEAPInterface = SLEAPInterfaceSchema.properties.SLEAPInterface; -globalStateCopy.schema.source_data.properties.FicTracDataInterface = - FicTracDataInterfaceSchema.properties.FicTracDataInterface; -globalStateCopy.schema.source_data.properties.AudioInterface = AudioInterfaceSchema.properties.AudioInterface; -globalStateCopy.schema.source_data.properties.MiniscopeBehaviorInterface = - MiniscopeBehaviorInterfaceSchema.properties.MiniscopeBehaviorInterface; -globalStateCopy.schema.source_data.properties.EDFRecordingInterface = - EDFRecordingInterfaceSchema.properties.EDFRecordingInterface; -globalStateCopy.schema.source_data.properties.SpikeGLXConverterPipe = - SpikeGLXConverterPipeSchema.properties.SpikeGLXConverterPipe; -globalStateCopy.schema.source_data.properties.BrukerTiffSinglePlaneConverter = - BrukerTiffSinglePlaneConverterSchema.properties.BrukerTiffSinglePlaneConverter; -globalStateCopy.schema.source_data.properties.BrukerTiffMultiPlaneConverter = - BrukerTiffMultiPlaneConverterSchema.properties.BrukerTiffMultiPlaneConverter; -globalStateCopy.schema.source_data.properties.MiniscopeConverter = - MiniscopeConverterSchema.properties.MiniscopeConverter; -globalStateCopy.schema.source_data.properties.CellExplorerRecordingInterface = - CellExplorerRecordingInterfaceSchema.properties.CellExplorerRecordingInterface; - -const results = globalStateCopy.results; -for (let sub in results) { - for (let ses in results[sub]) - results[sub][ses].source_data = { SpikeGLXNIDQInterface: { file_path: "/dummy/file/path" } }; -} - -export const All = PageTemplate.bind({}); -All.args = { activePage, globalState: globalStateCopy }; - -export const SpikeGLXRecordingInterface = PageTemplate.bind({}); -const SpikeGLXRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -SpikeGLXRecordingInterfaceGlobalCopy.interfaces.interface = SpikeGLXRecordingInterface; -SpikeGLXRecordingInterfaceGlobalCopy.schema.source_data = SpikeGLXRecordingInterfaceSchema; -SpikeGLXRecordingInterface.args = { activePage, globalState: SpikeGLXRecordingInterfaceGlobalCopy }; - -export const SpikeGLXNIDQInterface = PageTemplate.bind({}); -const SpikeGLXNIDQInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -SpikeGLXNIDQInterfaceGlobalCopy.interfaces.interface = SpikeGLXNIDQInterface; -SpikeGLXNIDQInterfaceGlobalCopy.schema.source_data = SpikeGLXNIDQInterfaceSchema; -SpikeGLXNIDQInterface.args = { activePage, globalState: SpikeGLXNIDQInterfaceGlobalCopy }; - -export const PhySortingInterface = PageTemplate.bind({}); -const PhySortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -PhySortingInterfaceGlobalCopy.interfaces.interface = PhySortingInterface; -PhySortingInterfaceGlobalCopy.schema.source_data = PhySortingInterfaceSchema; -PhySortingInterface.args = { activePage, globalState: PhySortingInterfaceGlobalCopy }; - -export const NeuroScopeRecordingInterface = PageTemplate.bind({}); -const NeuroScopeRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -NeuroScopeRecordingInterfaceGlobalCopy.interfaces.interface = NeuroScopeRecordingInterface; -NeuroScopeRecordingInterfaceGlobalCopy.schema.source_data = NeuroScopeRecordingInterfaceSchema; -NeuroScopeRecordingInterface.args = { activePage, globalState: NeuroScopeRecordingInterfaceGlobalCopy }; - -export const NeuroScopeLFPInterface = PageTemplate.bind({}); -const NeuroScopeLFPInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -NeuroScopeLFPInterfaceGlobalCopy.interfaces.interface = NeuroScopeLFPInterface; -NeuroScopeLFPInterfaceGlobalCopy.schema.source_data = NeuroScopeLFPInterfaceSchema; -NeuroScopeLFPInterface.args = { activePage, globalState: NeuroScopeLFPInterfaceGlobalCopy }; - -export const NeuroScopeSortingInterface = PageTemplate.bind({}); -const NeuroScopeSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -NeuroScopeSortingInterfaceGlobalCopy.interfaces.interface = NeuroScopeSortingInterface; -NeuroScopeSortingInterfaceGlobalCopy.schema.source_data = NeuroScopeSortingInterfaceSchema; -NeuroScopeSortingInterface.args = { activePage, globalState: NeuroScopeSortingInterfaceGlobalCopy }; - -export const BiocamRecordingInterface = PageTemplate.bind({}); -const BiocamRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -BiocamRecordingInterfaceGlobalCopy.interfaces.interface = BiocamRecordingInterface; -BiocamRecordingInterfaceGlobalCopy.schema.source_data = BiocamRecordingInterfaceSchema; -BiocamRecordingInterface.args = { activePage, globalState: BiocamRecordingInterfaceGlobalCopy }; - -export const IntanRecordingInterface = PageTemplate.bind({}); -const IntanRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -IntanRecordingInterfaceGlobalCopy.interfaces.interface = IntanRecordingInterface; -IntanRecordingInterfaceGlobalCopy.schema.source_data = IntanRecordingInterfaceSchema; -IntanRecordingInterface.args = { activePage, globalState: IntanRecordingInterfaceGlobalCopy }; - -export const OpenEphysRecordingInterface = PageTemplate.bind({}); -const OpenEphysRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -OpenEphysRecordingInterfaceGlobalCopy.interfaces.interface = OpenEphysRecordingInterface; -OpenEphysRecordingInterfaceGlobalCopy.schema.source_data = OpenEphysRecordingInterfaceSchema; -OpenEphysRecordingInterface.args = { activePage, globalState: OpenEphysRecordingInterfaceGlobalCopy }; - -export const BlackrockRecordingInterface = PageTemplate.bind({}); -const BlackrockRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -BlackrockRecordingInterfaceGlobalCopy.interfaces.interface = BlackrockRecordingInterface; -BlackrockRecordingInterfaceGlobalCopy.schema.source_data = BlackrockRecordingInterfaceSchema; -BlackrockRecordingInterface.args = { activePage, globalState: BlackrockRecordingInterfaceGlobalCopy }; - -export const BlackrockSortingInterface = PageTemplate.bind({}); -const BlackrockSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -BlackrockSortingInterfaceGlobalCopy.interfaces.interface = BlackrockSortingInterface; -BlackrockSortingInterfaceGlobalCopy.schema.source_data = BlackrockSortingInterfaceSchema; -BlackrockSortingInterface.args = { activePage, globalState: BlackrockSortingInterfaceGlobalCopy }; - -export const CellExplorerSortingInterface = PageTemplate.bind({}); -const CellExplorerSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -CellExplorerSortingInterfaceGlobalCopy.interfaces.interface = CellExplorerSortingInterface; -CellExplorerSortingInterfaceGlobalCopy.schema.source_data = CellExplorerSortingInterfaceSchema; -CellExplorerSortingInterface.args = { activePage, globalState: CellExplorerSortingInterfaceGlobalCopy }; - -export const KiloSortSortingInterface = PageTemplate.bind({}); -const KiloSortSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -KiloSortSortingInterfaceGlobalCopy.interfaces.interface = KiloSortSortingInterface; -KiloSortSortingInterfaceGlobalCopy.schema.source_data = KiloSortSortingInterfaceSchema; -KiloSortSortingInterface.args = { activePage, globalState: KiloSortSortingInterfaceGlobalCopy }; - -export const TdtRecordingInterface = PageTemplate.bind({}); -const TdtRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -TdtRecordingInterfaceGlobalCopy.interfaces.interface = TdtRecordingInterface; -TdtRecordingInterfaceGlobalCopy.schema.source_data = TdtRecordingInterfaceSchema; -TdtRecordingInterface.args = { activePage, globalState: TdtRecordingInterfaceGlobalCopy }; - -export const Spike2RecordingInterface = PageTemplate.bind({}); -const Spike2RecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -Spike2RecordingInterfaceGlobalCopy.interfaces.interface = Spike2RecordingInterface; -Spike2RecordingInterfaceGlobalCopy.schema.source_data = Spike2RecordingInterfaceSchema; -Spike2RecordingInterface.args = { activePage, globalState: Spike2RecordingInterfaceGlobalCopy }; - -export const BrukerTiffSinglePlaneImagingInterface = PageTemplate.bind({}); -const BrukerTiffSinglePlaneImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -BrukerTiffSinglePlaneImagingInterfaceGlobalCopy.interfaces.interface = BrukerTiffSinglePlaneImagingInterface; -BrukerTiffSinglePlaneImagingInterfaceGlobalCopy.schema.source_data = BrukerTiffSinglePlaneImagingInterfaceSchema; -BrukerTiffSinglePlaneImagingInterface.args = { - activePage, - globalState: BrukerTiffSinglePlaneImagingInterfaceGlobalCopy, -}; - -export const ExtractSegmentationInterface = PageTemplate.bind({}); -const ExtractSegmentationInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -ExtractSegmentationInterfaceGlobalCopy.interfaces.interface = ExtractSegmentationInterface; -ExtractSegmentationInterfaceGlobalCopy.schema.source_data = ExtractSegmentationInterfaceSchema; -ExtractSegmentationInterface.args = { activePage, globalState: ExtractSegmentationInterfaceGlobalCopy }; - -export const CnmfeSegmentationInterface = PageTemplate.bind({}); -const CnmfeSegmentationInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -CnmfeSegmentationInterfaceGlobalCopy.interfaces.interface = CnmfeSegmentationInterface; -CnmfeSegmentationInterfaceGlobalCopy.schema.source_data = CnmfeSegmentationInterfaceSchema; -CnmfeSegmentationInterface.args = { activePage, globalState: CnmfeSegmentationInterfaceGlobalCopy }; - -export const BrukerTiffMultiPlaneImagingInterface = PageTemplate.bind({}); -const BrukerTiffMultiPlaneImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -BrukerTiffMultiPlaneImagingInterfaceGlobalCopy.interfaces.interface = BrukerTiffMultiPlaneImagingInterface; -BrukerTiffMultiPlaneImagingInterfaceGlobalCopy.schema.source_data = BrukerTiffMultiPlaneImagingInterfaceSchema; -BrukerTiffMultiPlaneImagingInterface.args = { activePage, globalState: BrukerTiffMultiPlaneImagingInterfaceGlobalCopy }; - -export const MicroManagerTiffImagingInterface = PageTemplate.bind({}); -const MicroManagerTiffImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -MicroManagerTiffImagingInterfaceGlobalCopy.interfaces.interface = MicroManagerTiffImagingInterface; -MicroManagerTiffImagingInterfaceGlobalCopy.schema.source_data = MicroManagerTiffImagingInterfaceSchema; -MicroManagerTiffImagingInterface.args = { activePage, globalState: MicroManagerTiffImagingInterfaceGlobalCopy }; - -export const ScanImageImagingInterface = PageTemplate.bind({}); -const ScanImageImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -ScanImageImagingInterfaceGlobalCopy.interfaces.interface = ScanImageImagingInterface; -ScanImageImagingInterfaceGlobalCopy.schema.source_data = ScanImageImagingInterfaceSchema; -ScanImageImagingInterface.args = { activePage, globalState: ScanImageImagingInterfaceGlobalCopy }; - -export const TiffImagingInterface = PageTemplate.bind({}); -const TiffImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -TiffImagingInterfaceGlobalCopy.interfaces.interface = TiffImagingInterface; -TiffImagingInterfaceGlobalCopy.schema.source_data = TiffImagingInterfaceSchema; -TiffImagingInterface.args = { activePage, globalState: TiffImagingInterfaceGlobalCopy }; - -export const MiniscopeImagingInterface = PageTemplate.bind({}); -const MiniscopeImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -MiniscopeImagingInterfaceGlobalCopy.interfaces.interface = MiniscopeImagingInterface; -MiniscopeImagingInterfaceGlobalCopy.schema.source_data = MiniscopeImagingInterfaceSchema; -MiniscopeImagingInterface.args = { activePage, globalState: MiniscopeImagingInterfaceGlobalCopy }; - -export const SbxImagingInterface = PageTemplate.bind({}); -const SbxImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -SbxImagingInterfaceGlobalCopy.interfaces.interface = SbxImagingInterface; -SbxImagingInterfaceGlobalCopy.schema.source_data = SbxImagingInterfaceSchema; -SbxImagingInterface.args = { activePage, globalState: SbxImagingInterfaceGlobalCopy }; - -export const CaimanSegmentationInterface = PageTemplate.bind({}); -const CaimanSegmentationInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -CaimanSegmentationInterfaceGlobalCopy.interfaces.interface = CaimanSegmentationInterface; -CaimanSegmentationInterfaceGlobalCopy.schema.source_data = CaimanSegmentationInterfaceSchema; -CaimanSegmentationInterface.args = { activePage, globalState: CaimanSegmentationInterfaceGlobalCopy }; - -export const MCSRawRecordingInterface = PageTemplate.bind({}); -const MCSRawRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -MCSRawRecordingInterfaceGlobalCopy.interfaces.interface = MCSRawRecordingInterface; -MCSRawRecordingInterfaceGlobalCopy.schema.source_data = MCSRawRecordingInterfaceSchema; -MCSRawRecordingInterface.args = { activePage, globalState: MCSRawRecordingInterfaceGlobalCopy }; - -export const MEArecRecordingInterface = PageTemplate.bind({}); -const MEArecRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -MEArecRecordingInterfaceGlobalCopy.interfaces.interface = MEArecRecordingInterface; -MEArecRecordingInterfaceGlobalCopy.schema.source_data = MEArecRecordingInterfaceSchema; -MEArecRecordingInterface.args = { activePage, globalState: MEArecRecordingInterfaceGlobalCopy }; - -export const PlexonRecordingInterface = PageTemplate.bind({}); -const PlexonRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -PlexonRecordingInterfaceGlobalCopy.interfaces.interface = PlexonRecordingInterface; -PlexonRecordingInterfaceGlobalCopy.schema.source_data = PlexonRecordingInterfaceSchema; -PlexonRecordingInterface.args = { activePage, globalState: PlexonRecordingInterfaceGlobalCopy }; - -export const PlexonSortingInterface = PageTemplate.bind({}); -const PlexonSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -PlexonSortingInterfaceGlobalCopy.interfaces.interface = PlexonSortingInterface; -PlexonSortingInterfaceGlobalCopy.schema.source_data = PlexonSortingInterfaceSchema; -PlexonSortingInterface.args = { activePage, globalState: PlexonSortingInterfaceGlobalCopy }; - -export const AxonaRecordingInterface = PageTemplate.bind({}); -const AxonaRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -AxonaRecordingInterfaceGlobalCopy.interfaces.interface = AxonaRecordingInterface; -AxonaRecordingInterfaceGlobalCopy.schema.source_data = AxonaRecordingInterfaceSchema; -AxonaRecordingInterface.args = { activePage, globalState: AxonaRecordingInterfaceGlobalCopy }; - -export const VideoInterface = PageTemplate.bind({}); -const VideoInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -VideoInterfaceGlobalCopy.interfaces.interface = VideoInterface; -VideoInterfaceGlobalCopy.schema.source_data = VideoInterfaceSchema; -VideoInterface.args = { activePage, globalState: VideoInterfaceGlobalCopy }; - -export const NeuralynxRecordingInterface = PageTemplate.bind({}); -const NeuralynxRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -NeuralynxRecordingInterfaceGlobalCopy.interfaces.interface = NeuralynxRecordingInterface; -NeuralynxRecordingInterfaceGlobalCopy.schema.source_data = NeuralynxRecordingInterfaceSchema; -NeuralynxRecordingInterface.args = { activePage, globalState: NeuralynxRecordingInterfaceGlobalCopy }; - -export const Suite2pSegmentationInterface = PageTemplate.bind({}); -const Suite2pSegmentationInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -Suite2pSegmentationInterfaceGlobalCopy.interfaces.interface = Suite2pSegmentationInterface; -Suite2pSegmentationInterfaceGlobalCopy.schema.source_data = Suite2pSegmentationInterfaceSchema; -Suite2pSegmentationInterface.args = { activePage, globalState: Suite2pSegmentationInterfaceGlobalCopy }; - -export const AlphaOmegaRecordingInterface = PageTemplate.bind({}); -const AlphaOmegaRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -AlphaOmegaRecordingInterfaceGlobalCopy.interfaces.interface = AlphaOmegaRecordingInterface; -AlphaOmegaRecordingInterfaceGlobalCopy.schema.source_data = AlphaOmegaRecordingInterfaceSchema; -AlphaOmegaRecordingInterface.args = { activePage, globalState: AlphaOmegaRecordingInterfaceGlobalCopy }; - -export const DeepLabCutInterface = PageTemplate.bind({}); -const DeepLabCutInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -DeepLabCutInterfaceGlobalCopy.interfaces.interface = DeepLabCutInterface; -DeepLabCutInterfaceGlobalCopy.schema.source_data = DeepLabCutInterfaceSchema; -DeepLabCutInterface.args = { activePage, globalState: DeepLabCutInterfaceGlobalCopy }; - -export const SLEAPInterface = PageTemplate.bind({}); -const SLEAPInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -SLEAPInterfaceGlobalCopy.interfaces.interface = SLEAPInterface; -SLEAPInterfaceGlobalCopy.schema.source_data = SLEAPInterfaceSchema; -SLEAPInterface.args = { activePage, globalState: SLEAPInterfaceGlobalCopy }; - -export const FicTracDataInterface = PageTemplate.bind({}); -const FicTracDataInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -FicTracDataInterfaceGlobalCopy.interfaces.interface = FicTracDataInterface; -FicTracDataInterfaceGlobalCopy.schema.source_data = FicTracDataInterfaceSchema; -FicTracDataInterface.args = { activePage, globalState: FicTracDataInterfaceGlobalCopy }; - -export const AudioInterface = PageTemplate.bind({}); -const AudioInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -AudioInterfaceGlobalCopy.interfaces.interface = AudioInterface; -AudioInterfaceGlobalCopy.schema.source_data = AudioInterfaceSchema; -AudioInterface.args = { activePage, globalState: AudioInterfaceGlobalCopy }; - -export const MiniscopeBehaviorInterface = PageTemplate.bind({}); -const MiniscopeBehaviorInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -MiniscopeBehaviorInterfaceGlobalCopy.interfaces.interface = MiniscopeBehaviorInterface; -MiniscopeBehaviorInterfaceGlobalCopy.schema.source_data = MiniscopeBehaviorInterfaceSchema; -MiniscopeBehaviorInterface.args = { activePage, globalState: MiniscopeBehaviorInterfaceGlobalCopy }; - -export const EDFRecordingInterface = PageTemplate.bind({}); -const EDFRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -EDFRecordingInterfaceGlobalCopy.interfaces.interface = EDFRecordingInterface; -EDFRecordingInterfaceGlobalCopy.schema.source_data = EDFRecordingInterfaceSchema; -EDFRecordingInterface.args = { activePage, globalState: EDFRecordingInterfaceGlobalCopy }; - -export const SpikeGLXConverterPipe = PageTemplate.bind({}); -const SpikeGLXConverterPipeGlobalCopy = JSON.parse(JSON.stringify(globalState)); -SpikeGLXConverterPipeGlobalCopy.interfaces.interface = SpikeGLXConverterPipe; -SpikeGLXConverterPipeGlobalCopy.schema.source_data = SpikeGLXConverterPipeSchema; -SpikeGLXConverterPipe.args = { activePage, globalState: SpikeGLXConverterPipeGlobalCopy }; - -export const BrukerTiffSinglePlaneConverter = PageTemplate.bind({}); -const BrukerTiffSinglePlaneConverterGlobalCopy = JSON.parse(JSON.stringify(globalState)); -BrukerTiffSinglePlaneConverterGlobalCopy.interfaces.interface = BrukerTiffSinglePlaneConverter; -BrukerTiffSinglePlaneConverterGlobalCopy.schema.source_data = BrukerTiffSinglePlaneConverterSchema; -BrukerTiffSinglePlaneConverter.args = { activePage, globalState: BrukerTiffSinglePlaneConverterGlobalCopy }; - -export const BrukerTiffMultiPlaneConverter = PageTemplate.bind({}); -const BrukerTiffMultiPlaneConverterGlobalCopy = JSON.parse(JSON.stringify(globalState)); -BrukerTiffMultiPlaneConverterGlobalCopy.interfaces.interface = BrukerTiffMultiPlaneConverter; -BrukerTiffMultiPlaneConverterGlobalCopy.schema.source_data = BrukerTiffMultiPlaneConverterSchema; -BrukerTiffMultiPlaneConverter.args = { activePage, globalState: BrukerTiffMultiPlaneConverterGlobalCopy }; - -export const MiniscopeConverter = PageTemplate.bind({}); -const MiniscopeConverterGlobalCopy = JSON.parse(JSON.stringify(globalState)); -MiniscopeConverterGlobalCopy.interfaces.interface = MiniscopeConverter; -MiniscopeConverterGlobalCopy.schema.source_data = MiniscopeConverterSchema; -MiniscopeConverter.args = { activePage, globalState: MiniscopeConverterGlobalCopy }; - -export const CellExplorerRecordingInterface = PageTemplate.bind({}); -const CellExplorerRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); -CellExplorerRecordingInterfaceGlobalCopy.interfaces.interface = CellExplorerRecordingInterface; -CellExplorerRecordingInterfaceGlobalCopy.schema.source_data = CellExplorerRecordingInterfaceSchema; -CellExplorerRecordingInterface.args = { activePage, globalState: CellExplorerRecordingInterfaceGlobalCopy }; +import { globalState, PageTemplate } from "./storyStates"; +import SpikeGLXRecordingInterfaceSchema from "../inputs/interface_schemas/SpikeGLXRecordingInterface.json"; +import SpikeGLXNIDQInterfaceSchema from "../inputs/interface_schemas/SpikeGLXNIDQInterface.json"; +import PhySortingInterfaceSchema from "../inputs/interface_schemas/PhySortingInterface.json"; +import NeuroScopeRecordingInterfaceSchema from "../inputs/interface_schemas/NeuroScopeRecordingInterface.json"; +import NeuroScopeLFPInterfaceSchema from "../inputs/interface_schemas/NeuroScopeLFPInterface.json"; +import NeuroScopeSortingInterfaceSchema from "../inputs/interface_schemas/NeuroScopeSortingInterface.json"; +import BiocamRecordingInterfaceSchema from "../inputs/interface_schemas/BiocamRecordingInterface.json"; +import IntanRecordingInterfaceSchema from "../inputs/interface_schemas/IntanRecordingInterface.json"; +import OpenEphysRecordingInterfaceSchema from "../inputs/interface_schemas/OpenEphysRecordingInterface.json"; +import BlackrockRecordingInterfaceSchema from "../inputs/interface_schemas/BlackrockRecordingInterface.json"; +import BlackrockSortingInterfaceSchema from "../inputs/interface_schemas/BlackrockSortingInterface.json"; +import CellExplorerSortingInterfaceSchema from "../inputs/interface_schemas/CellExplorerSortingInterface.json"; +import KiloSortSortingInterfaceSchema from "../inputs/interface_schemas/KiloSortSortingInterface.json"; +import TdtRecordingInterfaceSchema from "../inputs/interface_schemas/TdtRecordingInterface.json"; +import Spike2RecordingInterfaceSchema from "../inputs/interface_schemas/Spike2RecordingInterface.json"; +import BrukerTiffSinglePlaneImagingInterfaceSchema from "../inputs/interface_schemas/BrukerTiffSinglePlaneImagingInterface.json"; +import ExtractSegmentationInterfaceSchema from "../inputs/interface_schemas/ExtractSegmentationInterface.json"; +import CnmfeSegmentationInterfaceSchema from "../inputs/interface_schemas/CnmfeSegmentationInterface.json"; +import BrukerTiffMultiPlaneImagingInterfaceSchema from "../inputs/interface_schemas/BrukerTiffMultiPlaneImagingInterface.json"; +import MicroManagerTiffImagingInterfaceSchema from "../inputs/interface_schemas/MicroManagerTiffImagingInterface.json"; +import ScanImageImagingInterfaceSchema from "../inputs/interface_schemas/ScanImageImagingInterface.json"; +import TiffImagingInterfaceSchema from "../inputs/interface_schemas/TiffImagingInterface.json"; +import MiniscopeImagingInterfaceSchema from "../inputs/interface_schemas/MiniscopeImagingInterface.json"; +import SbxImagingInterfaceSchema from "../inputs/interface_schemas/SbxImagingInterface.json"; +import CaimanSegmentationInterfaceSchema from "../inputs/interface_schemas/CaimanSegmentationInterface.json"; +import MCSRawRecordingInterfaceSchema from "../inputs/interface_schemas/MCSRawRecordingInterface.json"; +import MEArecRecordingInterfaceSchema from "../inputs/interface_schemas/MEArecRecordingInterface.json"; +import PlexonRecordingInterfaceSchema from "../inputs/interface_schemas/PlexonRecordingInterface.json"; +import PlexonSortingInterfaceSchema from "../inputs/interface_schemas/PlexonSortingInterface.json"; +import AxonaRecordingInterfaceSchema from "../inputs/interface_schemas/AxonaRecordingInterface.json"; +import VideoInterfaceSchema from "../inputs/interface_schemas/VideoInterface.json"; +import NeuralynxRecordingInterfaceSchema from "../inputs/interface_schemas/NeuralynxRecordingInterface.json"; +import Suite2pSegmentationInterfaceSchema from "../inputs/interface_schemas/Suite2pSegmentationInterface.json"; +import AlphaOmegaRecordingInterfaceSchema from "../inputs/interface_schemas/AlphaOmegaRecordingInterface.json"; +import DeepLabCutInterfaceSchema from "../inputs/interface_schemas/DeepLabCutInterface.json"; +import SLEAPInterfaceSchema from "../inputs/interface_schemas/SLEAPInterface.json"; +import FicTracDataInterfaceSchema from "../inputs/interface_schemas/FicTracDataInterface.json"; +import AudioInterfaceSchema from "../inputs/interface_schemas/AudioInterface.json"; +import MiniscopeBehaviorInterfaceSchema from "../inputs/interface_schemas/MiniscopeBehaviorInterface.json"; +import EDFRecordingInterfaceSchema from "../inputs/interface_schemas/EDFRecordingInterface.json"; +import SpikeGLXConverterPipeSchema from "../inputs/interface_schemas/SpikeGLXConverterPipe.json"; +import BrukerTiffSinglePlaneConverterSchema from "../inputs/interface_schemas/BrukerTiffSinglePlaneConverter.json"; +import BrukerTiffMultiPlaneConverterSchema from "../inputs/interface_schemas/BrukerTiffMultiPlaneConverter.json"; +import MiniscopeConverterSchema from "../inputs/interface_schemas/MiniscopeConverter.json"; +import CellExplorerRecordingInterfaceSchema from "../inputs/interface_schemas/CellExplorerRecordingInterface.json"; + +export default { + title: "Pages/Guided Mode/Source Data", + parameters: { + chromatic: { disableSnapshot: false }, + }, +}; + +const activePage = "//sourcedata"; + +const globalStateCopy = JSON.parse(JSON.stringify(globalState)); +globalStateCopy.schema.source_data.properties.SpikeGLXRecordingInterface = + SpikeGLXRecordingInterfaceSchema.properties.SpikeGLXRecordingInterface; +globalStateCopy.schema.source_data.properties.SpikeGLXNIDQInterface = + SpikeGLXNIDQInterfaceSchema.properties.SpikeGLXNIDQInterface; +globalStateCopy.schema.source_data.properties.PhySortingInterface = + PhySortingInterfaceSchema.properties.PhySortingInterface; +globalStateCopy.schema.source_data.properties.NeuroScopeRecordingInterface = + NeuroScopeRecordingInterfaceSchema.properties.NeuroScopeRecordingInterface; +globalStateCopy.schema.source_data.properties.NeuroScopeLFPInterface = + NeuroScopeLFPInterfaceSchema.properties.NeuroScopeLFPInterface; +globalStateCopy.schema.source_data.properties.NeuroScopeSortingInterface = + NeuroScopeSortingInterfaceSchema.properties.NeuroScopeSortingInterface; +globalStateCopy.schema.source_data.properties.BiocamRecordingInterface = + BiocamRecordingInterfaceSchema.properties.BiocamRecordingInterface; +globalStateCopy.schema.source_data.properties.IntanRecordingInterface = + IntanRecordingInterfaceSchema.properties.IntanRecordingInterface; +globalStateCopy.schema.source_data.properties.OpenEphysRecordingInterface = + OpenEphysRecordingInterfaceSchema.properties.OpenEphysRecordingInterface; +globalStateCopy.schema.source_data.properties.BlackrockRecordingInterface = + BlackrockRecordingInterfaceSchema.properties.BlackrockRecordingInterface; +globalStateCopy.schema.source_data.properties.BlackrockSortingInterface = + BlackrockSortingInterfaceSchema.properties.BlackrockSortingInterface; +globalStateCopy.schema.source_data.properties.CellExplorerSortingInterface = + CellExplorerSortingInterfaceSchema.properties.CellExplorerSortingInterface; +globalStateCopy.schema.source_data.properties.KiloSortSortingInterface = + KiloSortSortingInterfaceSchema.properties.KiloSortSortingInterface; +globalStateCopy.schema.source_data.properties.TdtRecordingInterface = + TdtRecordingInterfaceSchema.properties.TdtRecordingInterface; +globalStateCopy.schema.source_data.properties.Spike2RecordingInterface = + Spike2RecordingInterfaceSchema.properties.Spike2RecordingInterface; +globalStateCopy.schema.source_data.properties.BrukerTiffSinglePlaneImagingInterface = + BrukerTiffSinglePlaneImagingInterfaceSchema.properties.BrukerTiffSinglePlaneImagingInterface; +globalStateCopy.schema.source_data.properties.ExtractSegmentationInterface = + ExtractSegmentationInterfaceSchema.properties.ExtractSegmentationInterface; +globalStateCopy.schema.source_data.properties.CnmfeSegmentationInterface = + CnmfeSegmentationInterfaceSchema.properties.CnmfeSegmentationInterface; +globalStateCopy.schema.source_data.properties.BrukerTiffMultiPlaneImagingInterface = + BrukerTiffMultiPlaneImagingInterfaceSchema.properties.BrukerTiffMultiPlaneImagingInterface; +globalStateCopy.schema.source_data.properties.MicroManagerTiffImagingInterface = + MicroManagerTiffImagingInterfaceSchema.properties.MicroManagerTiffImagingInterface; +globalStateCopy.schema.source_data.properties.ScanImageImagingInterface = + ScanImageImagingInterfaceSchema.properties.ScanImageImagingInterface; +globalStateCopy.schema.source_data.properties.TiffImagingInterface = + TiffImagingInterfaceSchema.properties.TiffImagingInterface; +globalStateCopy.schema.source_data.properties.MiniscopeImagingInterface = + MiniscopeImagingInterfaceSchema.properties.MiniscopeImagingInterface; +globalStateCopy.schema.source_data.properties.SbxImagingInterface = + SbxImagingInterfaceSchema.properties.SbxImagingInterface; +globalStateCopy.schema.source_data.properties.CaimanSegmentationInterface = + CaimanSegmentationInterfaceSchema.properties.CaimanSegmentationInterface; +globalStateCopy.schema.source_data.properties.MCSRawRecordingInterface = + MCSRawRecordingInterfaceSchema.properties.MCSRawRecordingInterface; +globalStateCopy.schema.source_data.properties.MEArecRecordingInterface = + MEArecRecordingInterfaceSchema.properties.MEArecRecordingInterface; +globalStateCopy.schema.source_data.properties.PlexonRecordingInterface = + PlexonRecordingInterfaceSchema.properties.PlexonRecordingInterface; +globalStateCopy.schema.source_data.properties.PlexonSortingInterface = + PlexonSortingInterfaceSchema.properties.PlexonSortingInterface; +globalStateCopy.schema.source_data.properties.AxonaRecordingInterface = + AxonaRecordingInterfaceSchema.properties.AxonaRecordingInterface; +globalStateCopy.schema.source_data.properties.VideoInterface = VideoInterfaceSchema.properties.VideoInterface; +globalStateCopy.schema.source_data.properties.NeuralynxRecordingInterface = + NeuralynxRecordingInterfaceSchema.properties.NeuralynxRecordingInterface; +globalStateCopy.schema.source_data.properties.Suite2pSegmentationInterface = + Suite2pSegmentationInterfaceSchema.properties.Suite2pSegmentationInterface; +globalStateCopy.schema.source_data.properties.AlphaOmegaRecordingInterface = + AlphaOmegaRecordingInterfaceSchema.properties.AlphaOmegaRecordingInterface; +globalStateCopy.schema.source_data.properties.DeepLabCutInterface = + DeepLabCutInterfaceSchema.properties.DeepLabCutInterface; +globalStateCopy.schema.source_data.properties.SLEAPInterface = SLEAPInterfaceSchema.properties.SLEAPInterface; +globalStateCopy.schema.source_data.properties.FicTracDataInterface = + FicTracDataInterfaceSchema.properties.FicTracDataInterface; +globalStateCopy.schema.source_data.properties.AudioInterface = AudioInterfaceSchema.properties.AudioInterface; +globalStateCopy.schema.source_data.properties.MiniscopeBehaviorInterface = + MiniscopeBehaviorInterfaceSchema.properties.MiniscopeBehaviorInterface; +globalStateCopy.schema.source_data.properties.EDFRecordingInterface = + EDFRecordingInterfaceSchema.properties.EDFRecordingInterface; +globalStateCopy.schema.source_data.properties.SpikeGLXConverterPipe = + SpikeGLXConverterPipeSchema.properties.SpikeGLXConverterPipe; +globalStateCopy.schema.source_data.properties.BrukerTiffSinglePlaneConverter = + BrukerTiffSinglePlaneConverterSchema.properties.BrukerTiffSinglePlaneConverter; +globalStateCopy.schema.source_data.properties.BrukerTiffMultiPlaneConverter = + BrukerTiffMultiPlaneConverterSchema.properties.BrukerTiffMultiPlaneConverter; +globalStateCopy.schema.source_data.properties.MiniscopeConverter = + MiniscopeConverterSchema.properties.MiniscopeConverter; +globalStateCopy.schema.source_data.properties.CellExplorerRecordingInterface = + CellExplorerRecordingInterfaceSchema.properties.CellExplorerRecordingInterface; + +const results = globalStateCopy.results; +for (let sub in results) { + for (let ses in results[sub]) + results[sub][ses].source_data = { SpikeGLXNIDQInterface: { file_path: "/dummy/file/path" } }; +} + +export const All = PageTemplate.bind({}); +All.args = { activePage, globalState: globalStateCopy }; + +export const SpikeGLXRecordingInterface = PageTemplate.bind({}); +const SpikeGLXRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +SpikeGLXRecordingInterfaceGlobalCopy.interfaces.interface = SpikeGLXRecordingInterface; +SpikeGLXRecordingInterfaceGlobalCopy.schema.source_data = SpikeGLXRecordingInterfaceSchema; +SpikeGLXRecordingInterface.args = { activePage, globalState: SpikeGLXRecordingInterfaceGlobalCopy }; + +export const SpikeGLXNIDQInterface = PageTemplate.bind({}); +const SpikeGLXNIDQInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +SpikeGLXNIDQInterfaceGlobalCopy.interfaces.interface = SpikeGLXNIDQInterface; +SpikeGLXNIDQInterfaceGlobalCopy.schema.source_data = SpikeGLXNIDQInterfaceSchema; +SpikeGLXNIDQInterface.args = { activePage, globalState: SpikeGLXNIDQInterfaceGlobalCopy }; + +export const PhySortingInterface = PageTemplate.bind({}); +const PhySortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +PhySortingInterfaceGlobalCopy.interfaces.interface = PhySortingInterface; +PhySortingInterfaceGlobalCopy.schema.source_data = PhySortingInterfaceSchema; +PhySortingInterface.args = { activePage, globalState: PhySortingInterfaceGlobalCopy }; + +export const NeuroScopeRecordingInterface = PageTemplate.bind({}); +const NeuroScopeRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +NeuroScopeRecordingInterfaceGlobalCopy.interfaces.interface = NeuroScopeRecordingInterface; +NeuroScopeRecordingInterfaceGlobalCopy.schema.source_data = NeuroScopeRecordingInterfaceSchema; +NeuroScopeRecordingInterface.args = { activePage, globalState: NeuroScopeRecordingInterfaceGlobalCopy }; + +export const NeuroScopeLFPInterface = PageTemplate.bind({}); +const NeuroScopeLFPInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +NeuroScopeLFPInterfaceGlobalCopy.interfaces.interface = NeuroScopeLFPInterface; +NeuroScopeLFPInterfaceGlobalCopy.schema.source_data = NeuroScopeLFPInterfaceSchema; +NeuroScopeLFPInterface.args = { activePage, globalState: NeuroScopeLFPInterfaceGlobalCopy }; + +export const NeuroScopeSortingInterface = PageTemplate.bind({}); +const NeuroScopeSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +NeuroScopeSortingInterfaceGlobalCopy.interfaces.interface = NeuroScopeSortingInterface; +NeuroScopeSortingInterfaceGlobalCopy.schema.source_data = NeuroScopeSortingInterfaceSchema; +NeuroScopeSortingInterface.args = { activePage, globalState: NeuroScopeSortingInterfaceGlobalCopy }; + +export const BiocamRecordingInterface = PageTemplate.bind({}); +const BiocamRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +BiocamRecordingInterfaceGlobalCopy.interfaces.interface = BiocamRecordingInterface; +BiocamRecordingInterfaceGlobalCopy.schema.source_data = BiocamRecordingInterfaceSchema; +BiocamRecordingInterface.args = { activePage, globalState: BiocamRecordingInterfaceGlobalCopy }; + +export const IntanRecordingInterface = PageTemplate.bind({}); +const IntanRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +IntanRecordingInterfaceGlobalCopy.interfaces.interface = IntanRecordingInterface; +IntanRecordingInterfaceGlobalCopy.schema.source_data = IntanRecordingInterfaceSchema; +IntanRecordingInterface.args = { activePage, globalState: IntanRecordingInterfaceGlobalCopy }; + +export const OpenEphysRecordingInterface = PageTemplate.bind({}); +const OpenEphysRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +OpenEphysRecordingInterfaceGlobalCopy.interfaces.interface = OpenEphysRecordingInterface; +OpenEphysRecordingInterfaceGlobalCopy.schema.source_data = OpenEphysRecordingInterfaceSchema; +OpenEphysRecordingInterface.args = { activePage, globalState: OpenEphysRecordingInterfaceGlobalCopy }; + +export const BlackrockRecordingInterface = PageTemplate.bind({}); +const BlackrockRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +BlackrockRecordingInterfaceGlobalCopy.interfaces.interface = BlackrockRecordingInterface; +BlackrockRecordingInterfaceGlobalCopy.schema.source_data = BlackrockRecordingInterfaceSchema; +BlackrockRecordingInterface.args = { activePage, globalState: BlackrockRecordingInterfaceGlobalCopy }; + +export const BlackrockSortingInterface = PageTemplate.bind({}); +const BlackrockSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +BlackrockSortingInterfaceGlobalCopy.interfaces.interface = BlackrockSortingInterface; +BlackrockSortingInterfaceGlobalCopy.schema.source_data = BlackrockSortingInterfaceSchema; +BlackrockSortingInterface.args = { activePage, globalState: BlackrockSortingInterfaceGlobalCopy }; + +export const CellExplorerSortingInterface = PageTemplate.bind({}); +const CellExplorerSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +CellExplorerSortingInterfaceGlobalCopy.interfaces.interface = CellExplorerSortingInterface; +CellExplorerSortingInterfaceGlobalCopy.schema.source_data = CellExplorerSortingInterfaceSchema; +CellExplorerSortingInterface.args = { activePage, globalState: CellExplorerSortingInterfaceGlobalCopy }; + +export const KiloSortSortingInterface = PageTemplate.bind({}); +const KiloSortSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +KiloSortSortingInterfaceGlobalCopy.interfaces.interface = KiloSortSortingInterface; +KiloSortSortingInterfaceGlobalCopy.schema.source_data = KiloSortSortingInterfaceSchema; +KiloSortSortingInterface.args = { activePage, globalState: KiloSortSortingInterfaceGlobalCopy }; + +export const TdtRecordingInterface = PageTemplate.bind({}); +const TdtRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +TdtRecordingInterfaceGlobalCopy.interfaces.interface = TdtRecordingInterface; +TdtRecordingInterfaceGlobalCopy.schema.source_data = TdtRecordingInterfaceSchema; +TdtRecordingInterface.args = { activePage, globalState: TdtRecordingInterfaceGlobalCopy }; + +export const Spike2RecordingInterface = PageTemplate.bind({}); +const Spike2RecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +Spike2RecordingInterfaceGlobalCopy.interfaces.interface = Spike2RecordingInterface; +Spike2RecordingInterfaceGlobalCopy.schema.source_data = Spike2RecordingInterfaceSchema; +Spike2RecordingInterface.args = { activePage, globalState: Spike2RecordingInterfaceGlobalCopy }; + +export const BrukerTiffSinglePlaneImagingInterface = PageTemplate.bind({}); +const BrukerTiffSinglePlaneImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +BrukerTiffSinglePlaneImagingInterfaceGlobalCopy.interfaces.interface = BrukerTiffSinglePlaneImagingInterface; +BrukerTiffSinglePlaneImagingInterfaceGlobalCopy.schema.source_data = BrukerTiffSinglePlaneImagingInterfaceSchema; +BrukerTiffSinglePlaneImagingInterface.args = { + activePage, + globalState: BrukerTiffSinglePlaneImagingInterfaceGlobalCopy, +}; + +export const ExtractSegmentationInterface = PageTemplate.bind({}); +const ExtractSegmentationInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +ExtractSegmentationInterfaceGlobalCopy.interfaces.interface = ExtractSegmentationInterface; +ExtractSegmentationInterfaceGlobalCopy.schema.source_data = ExtractSegmentationInterfaceSchema; +ExtractSegmentationInterface.args = { activePage, globalState: ExtractSegmentationInterfaceGlobalCopy }; + +export const CnmfeSegmentationInterface = PageTemplate.bind({}); +const CnmfeSegmentationInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +CnmfeSegmentationInterfaceGlobalCopy.interfaces.interface = CnmfeSegmentationInterface; +CnmfeSegmentationInterfaceGlobalCopy.schema.source_data = CnmfeSegmentationInterfaceSchema; +CnmfeSegmentationInterface.args = { activePage, globalState: CnmfeSegmentationInterfaceGlobalCopy }; + +export const BrukerTiffMultiPlaneImagingInterface = PageTemplate.bind({}); +const BrukerTiffMultiPlaneImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +BrukerTiffMultiPlaneImagingInterfaceGlobalCopy.interfaces.interface = BrukerTiffMultiPlaneImagingInterface; +BrukerTiffMultiPlaneImagingInterfaceGlobalCopy.schema.source_data = BrukerTiffMultiPlaneImagingInterfaceSchema; +BrukerTiffMultiPlaneImagingInterface.args = { activePage, globalState: BrukerTiffMultiPlaneImagingInterfaceGlobalCopy }; + +export const MicroManagerTiffImagingInterface = PageTemplate.bind({}); +const MicroManagerTiffImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +MicroManagerTiffImagingInterfaceGlobalCopy.interfaces.interface = MicroManagerTiffImagingInterface; +MicroManagerTiffImagingInterfaceGlobalCopy.schema.source_data = MicroManagerTiffImagingInterfaceSchema; +MicroManagerTiffImagingInterface.args = { activePage, globalState: MicroManagerTiffImagingInterfaceGlobalCopy }; + +export const ScanImageImagingInterface = PageTemplate.bind({}); +const ScanImageImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +ScanImageImagingInterfaceGlobalCopy.interfaces.interface = ScanImageImagingInterface; +ScanImageImagingInterfaceGlobalCopy.schema.source_data = ScanImageImagingInterfaceSchema; +ScanImageImagingInterface.args = { activePage, globalState: ScanImageImagingInterfaceGlobalCopy }; + +export const TiffImagingInterface = PageTemplate.bind({}); +const TiffImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +TiffImagingInterfaceGlobalCopy.interfaces.interface = TiffImagingInterface; +TiffImagingInterfaceGlobalCopy.schema.source_data = TiffImagingInterfaceSchema; +TiffImagingInterface.args = { activePage, globalState: TiffImagingInterfaceGlobalCopy }; + +export const MiniscopeImagingInterface = PageTemplate.bind({}); +const MiniscopeImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +MiniscopeImagingInterfaceGlobalCopy.interfaces.interface = MiniscopeImagingInterface; +MiniscopeImagingInterfaceGlobalCopy.schema.source_data = MiniscopeImagingInterfaceSchema; +MiniscopeImagingInterface.args = { activePage, globalState: MiniscopeImagingInterfaceGlobalCopy }; + +export const SbxImagingInterface = PageTemplate.bind({}); +const SbxImagingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +SbxImagingInterfaceGlobalCopy.interfaces.interface = SbxImagingInterface; +SbxImagingInterfaceGlobalCopy.schema.source_data = SbxImagingInterfaceSchema; +SbxImagingInterface.args = { activePage, globalState: SbxImagingInterfaceGlobalCopy }; + +export const CaimanSegmentationInterface = PageTemplate.bind({}); +const CaimanSegmentationInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +CaimanSegmentationInterfaceGlobalCopy.interfaces.interface = CaimanSegmentationInterface; +CaimanSegmentationInterfaceGlobalCopy.schema.source_data = CaimanSegmentationInterfaceSchema; +CaimanSegmentationInterface.args = { activePage, globalState: CaimanSegmentationInterfaceGlobalCopy }; + +export const MCSRawRecordingInterface = PageTemplate.bind({}); +const MCSRawRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +MCSRawRecordingInterfaceGlobalCopy.interfaces.interface = MCSRawRecordingInterface; +MCSRawRecordingInterfaceGlobalCopy.schema.source_data = MCSRawRecordingInterfaceSchema; +MCSRawRecordingInterface.args = { activePage, globalState: MCSRawRecordingInterfaceGlobalCopy }; + +export const MEArecRecordingInterface = PageTemplate.bind({}); +const MEArecRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +MEArecRecordingInterfaceGlobalCopy.interfaces.interface = MEArecRecordingInterface; +MEArecRecordingInterfaceGlobalCopy.schema.source_data = MEArecRecordingInterfaceSchema; +MEArecRecordingInterface.args = { activePage, globalState: MEArecRecordingInterfaceGlobalCopy }; + +export const PlexonRecordingInterface = PageTemplate.bind({}); +const PlexonRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +PlexonRecordingInterfaceGlobalCopy.interfaces.interface = PlexonRecordingInterface; +PlexonRecordingInterfaceGlobalCopy.schema.source_data = PlexonRecordingInterfaceSchema; +PlexonRecordingInterface.args = { activePage, globalState: PlexonRecordingInterfaceGlobalCopy }; + +export const PlexonSortingInterface = PageTemplate.bind({}); +const PlexonSortingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +PlexonSortingInterfaceGlobalCopy.interfaces.interface = PlexonSortingInterface; +PlexonSortingInterfaceGlobalCopy.schema.source_data = PlexonSortingInterfaceSchema; +PlexonSortingInterface.args = { activePage, globalState: PlexonSortingInterfaceGlobalCopy }; + +export const AxonaRecordingInterface = PageTemplate.bind({}); +const AxonaRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +AxonaRecordingInterfaceGlobalCopy.interfaces.interface = AxonaRecordingInterface; +AxonaRecordingInterfaceGlobalCopy.schema.source_data = AxonaRecordingInterfaceSchema; +AxonaRecordingInterface.args = { activePage, globalState: AxonaRecordingInterfaceGlobalCopy }; + +export const VideoInterface = PageTemplate.bind({}); +const VideoInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +VideoInterfaceGlobalCopy.interfaces.interface = VideoInterface; +VideoInterfaceGlobalCopy.schema.source_data = VideoInterfaceSchema; +VideoInterface.args = { activePage, globalState: VideoInterfaceGlobalCopy }; + +export const NeuralynxRecordingInterface = PageTemplate.bind({}); +const NeuralynxRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +NeuralynxRecordingInterfaceGlobalCopy.interfaces.interface = NeuralynxRecordingInterface; +NeuralynxRecordingInterfaceGlobalCopy.schema.source_data = NeuralynxRecordingInterfaceSchema; +NeuralynxRecordingInterface.args = { activePage, globalState: NeuralynxRecordingInterfaceGlobalCopy }; + +export const Suite2pSegmentationInterface = PageTemplate.bind({}); +const Suite2pSegmentationInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +Suite2pSegmentationInterfaceGlobalCopy.interfaces.interface = Suite2pSegmentationInterface; +Suite2pSegmentationInterfaceGlobalCopy.schema.source_data = Suite2pSegmentationInterfaceSchema; +Suite2pSegmentationInterface.args = { activePage, globalState: Suite2pSegmentationInterfaceGlobalCopy }; + +export const AlphaOmegaRecordingInterface = PageTemplate.bind({}); +const AlphaOmegaRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +AlphaOmegaRecordingInterfaceGlobalCopy.interfaces.interface = AlphaOmegaRecordingInterface; +AlphaOmegaRecordingInterfaceGlobalCopy.schema.source_data = AlphaOmegaRecordingInterfaceSchema; +AlphaOmegaRecordingInterface.args = { activePage, globalState: AlphaOmegaRecordingInterfaceGlobalCopy }; + +export const DeepLabCutInterface = PageTemplate.bind({}); +const DeepLabCutInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +DeepLabCutInterfaceGlobalCopy.interfaces.interface = DeepLabCutInterface; +DeepLabCutInterfaceGlobalCopy.schema.source_data = DeepLabCutInterfaceSchema; +DeepLabCutInterface.args = { activePage, globalState: DeepLabCutInterfaceGlobalCopy }; + +export const SLEAPInterface = PageTemplate.bind({}); +const SLEAPInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +SLEAPInterfaceGlobalCopy.interfaces.interface = SLEAPInterface; +SLEAPInterfaceGlobalCopy.schema.source_data = SLEAPInterfaceSchema; +SLEAPInterface.args = { activePage, globalState: SLEAPInterfaceGlobalCopy }; + +export const FicTracDataInterface = PageTemplate.bind({}); +const FicTracDataInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +FicTracDataInterfaceGlobalCopy.interfaces.interface = FicTracDataInterface; +FicTracDataInterfaceGlobalCopy.schema.source_data = FicTracDataInterfaceSchema; +FicTracDataInterface.args = { activePage, globalState: FicTracDataInterfaceGlobalCopy }; + +export const AudioInterface = PageTemplate.bind({}); +const AudioInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +AudioInterfaceGlobalCopy.interfaces.interface = AudioInterface; +AudioInterfaceGlobalCopy.schema.source_data = AudioInterfaceSchema; +AudioInterface.args = { activePage, globalState: AudioInterfaceGlobalCopy }; + +export const MiniscopeBehaviorInterface = PageTemplate.bind({}); +const MiniscopeBehaviorInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +MiniscopeBehaviorInterfaceGlobalCopy.interfaces.interface = MiniscopeBehaviorInterface; +MiniscopeBehaviorInterfaceGlobalCopy.schema.source_data = MiniscopeBehaviorInterfaceSchema; +MiniscopeBehaviorInterface.args = { activePage, globalState: MiniscopeBehaviorInterfaceGlobalCopy }; + +export const EDFRecordingInterface = PageTemplate.bind({}); +const EDFRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +EDFRecordingInterfaceGlobalCopy.interfaces.interface = EDFRecordingInterface; +EDFRecordingInterfaceGlobalCopy.schema.source_data = EDFRecordingInterfaceSchema; +EDFRecordingInterface.args = { activePage, globalState: EDFRecordingInterfaceGlobalCopy }; + +export const SpikeGLXConverterPipe = PageTemplate.bind({}); +const SpikeGLXConverterPipeGlobalCopy = JSON.parse(JSON.stringify(globalState)); +SpikeGLXConverterPipeGlobalCopy.interfaces.interface = SpikeGLXConverterPipe; +SpikeGLXConverterPipeGlobalCopy.schema.source_data = SpikeGLXConverterPipeSchema; +SpikeGLXConverterPipe.args = { activePage, globalState: SpikeGLXConverterPipeGlobalCopy }; + +export const BrukerTiffSinglePlaneConverter = PageTemplate.bind({}); +const BrukerTiffSinglePlaneConverterGlobalCopy = JSON.parse(JSON.stringify(globalState)); +BrukerTiffSinglePlaneConverterGlobalCopy.interfaces.interface = BrukerTiffSinglePlaneConverter; +BrukerTiffSinglePlaneConverterGlobalCopy.schema.source_data = BrukerTiffSinglePlaneConverterSchema; +BrukerTiffSinglePlaneConverter.args = { activePage, globalState: BrukerTiffSinglePlaneConverterGlobalCopy }; + +export const BrukerTiffMultiPlaneConverter = PageTemplate.bind({}); +const BrukerTiffMultiPlaneConverterGlobalCopy = JSON.parse(JSON.stringify(globalState)); +BrukerTiffMultiPlaneConverterGlobalCopy.interfaces.interface = BrukerTiffMultiPlaneConverter; +BrukerTiffMultiPlaneConverterGlobalCopy.schema.source_data = BrukerTiffMultiPlaneConverterSchema; +BrukerTiffMultiPlaneConverter.args = { activePage, globalState: BrukerTiffMultiPlaneConverterGlobalCopy }; + +export const MiniscopeConverter = PageTemplate.bind({}); +const MiniscopeConverterGlobalCopy = JSON.parse(JSON.stringify(globalState)); +MiniscopeConverterGlobalCopy.interfaces.interface = MiniscopeConverter; +MiniscopeConverterGlobalCopy.schema.source_data = MiniscopeConverterSchema; +MiniscopeConverter.args = { activePage, globalState: MiniscopeConverterGlobalCopy }; + +export const CellExplorerRecordingInterface = PageTemplate.bind({}); +const CellExplorerRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +CellExplorerRecordingInterfaceGlobalCopy.interfaces.interface = CellExplorerRecordingInterface; +CellExplorerRecordingInterfaceGlobalCopy.schema.source_data = CellExplorerRecordingInterfaceSchema; +CellExplorerRecordingInterface.args = { activePage, globalState: CellExplorerRecordingInterfaceGlobalCopy }; From 32c544f9573e19fddb1bb4cddb8f2c95eb77207c Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 28 May 2024 14:58:53 -0700 Subject: [PATCH 3/6] Update pages.js --- src/electron/renderer/src/pages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/renderer/src/pages.js b/src/electron/renderer/src/pages.js index d203b6512..5b5e4117a 100644 --- a/src/electron/renderer/src/pages.js +++ b/src/electron/renderer/src/pages.js @@ -16,7 +16,7 @@ import { GuidedInspectorPage } from "./stories/pages/guided-mode/options/GuidedI import logo from "../assets/img/logo-guide-draft-transparent-tight.png"; import { GuidedPathExpansionPage } from "./stories/pages/guided-mode/data/GuidedPathExpansion"; -import uploadIcon from "../assets/icons/dandi.svg"; +import uploadIcon from "../assets/icons/dandi.svg?raw"; import inspectIcon from "../assets/icons/inspect.svg?raw"; import neurosiftIcon from "../assets/icons/neurosift-logo.svg?raw"; From 07991fa0de555f3365d9ccb3f651d01e7e1e0be9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 21:59:28 +0000 Subject: [PATCH 4/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/electron/renderer/src/pages.js | 390 ++++++++++++++--------------- 1 file changed, 195 insertions(+), 195 deletions(-) diff --git a/src/electron/renderer/src/pages.js b/src/electron/renderer/src/pages.js index d203b6512..0dc468955 100644 --- a/src/electron/renderer/src/pages.js +++ b/src/electron/renderer/src/pages.js @@ -1,195 +1,195 @@ -import { GettingStartedPage } from "./stories/pages/getting-started/GettingStarted"; -import { DocumentationPage } from "./stories/pages/documentation/Documentation"; -import { ContactPage } from "./stories/pages/contact-us/Contact"; -import { GuidedHomePage } from "./stories/pages/guided-mode/GuidedHome"; -import { GuidedNewDatasetPage } from "./stories/pages/guided-mode/setup/GuidedNewDatasetInfo"; -import { GuidedStructurePage } from "./stories/pages/guided-mode/data/GuidedStructure"; -import { sections } from "./stories/pages/globals"; -import { GuidedSubjectsPage } from "./stories/pages/guided-mode/setup/GuidedSubjects"; -import { GuidedSourceDataPage } from "./stories/pages/guided-mode/data/GuidedSourceData"; -import { GuidedMetadataPage } from "./stories/pages/guided-mode/data/GuidedMetadata"; -import { GuidedUploadPage } from "./stories/pages/guided-mode/options/GuidedUpload"; -import { GuidedResultsPage } from "./stories/pages/guided-mode/results/GuidedResults"; -import { Dashboard } from "./stories/Dashboard"; -import { GuidedStubPreviewPage } from "./stories/pages/guided-mode/options/GuidedStubPreview"; -import { GuidedInspectorPage } from "./stories/pages/guided-mode/options/GuidedInspectorPage"; - -import logo from "../assets/img/logo-guide-draft-transparent-tight.png"; -import { GuidedPathExpansionPage } from "./stories/pages/guided-mode/data/GuidedPathExpansion"; -import uploadIcon from "../assets/icons/dandi.svg"; -import inspectIcon from "../assets/icons/inspect.svg?raw"; -import neurosiftIcon from "../assets/icons/neurosift-logo.svg?raw"; - -import settingsIcon from "../assets/icons/settings.svg?raw"; - -import { UploadsPage } from "./stories/pages/uploads/UploadsPage"; -import { SettingsPage } from "./stories/pages/settings/SettingsPage"; -import { InspectPage } from "./stories/pages/inspect/InspectPage"; -import { PreviewPage } from "./stories/pages/preview/PreviewPage"; -import { GuidedPreform } from "./stories/pages/guided-mode/setup/Preform"; -import { GuidedDandiResultsPage } from "./stories/pages/guided-mode/results/GuidedDandiResults"; - -let dashboard = document.querySelector("nwb-dashboard"); -if (!dashboard) dashboard = new Dashboard(); -dashboard.logo = logo; -dashboard.name = "NWB GUIDE"; -dashboard.renderNameInSidebar = false; - -const resourcesGroup = "Resources"; - -const guidedIcon = ` - - - - -`; - -const documentationIcon = ` - - -`; - -const contactIcon = ` - -`; - -const pages = { - "/": new GuidedHomePage({ - label: "Convert", - icon: guidedIcon, - pages: { - details: new GuidedNewDatasetPage({ - title: "Project Setup", - label: "Project details", - section: sections[0], - }), - - workflow: new GuidedPreform({ - title: "Pipeline Workflow", - label: "Pipeline workflow", - section: sections[0], - }), - - structure: new GuidedStructurePage({ - title: "Provide Data Formats", - label: "Data formats", - section: sections[0], - }), - - locate: new GuidedPathExpansionPage({ - title: "Locate Data", - label: "Locate data", - section: sections[0], - }), - - subjects: new GuidedSubjectsPage({ - title: "Subject Metadata", - label: "Subject details", - section: sections[0], - }), - - sourcedata: new GuidedSourceDataPage({ - title: "Source Data Information", - label: "Source data", - section: sections[1], - }), - - metadata: new GuidedMetadataPage({ - title: "File Metadata", - label: "File metadata", - section: sections[1], - }), - - inspect: new GuidedInspectorPage({ - title: "Inspector Report", - label: "Validate metadata", - section: sections[2], - sync: ["preview"], - }), - - preview: new GuidedStubPreviewPage({ - title: "Conversion Preview", - label: "Preview NWB files", - section: sections[2], - sync: ["preview"], - }), - - conversion: new GuidedResultsPage({ - title: "Conversion Review", - label: "Review conversion", - section: sections[2], - sync: ["conversion"], - }), - - upload: new GuidedUploadPage({ - title: "DANDI Upload", - label: "Upload to DANDI", - section: sections[3], - sync: ["conversion"], - }), - - review: new GuidedDandiResultsPage({ - title: "Upload Review", - label: "Review published data", - section: sections[3], - }), - }, - }), - validate: new InspectPage({ - label: "Validate", - icon: inspectIcon, - }), - explore: new PreviewPage({ - label: "Explore", - icon: neurosiftIcon, - }), - uploads: new UploadsPage({ - label: "Upload", - icon: uploadIcon, - }), - docs: new DocumentationPage({ - label: "Documentation", - icon: documentationIcon, - group: resourcesGroup, - }), - contact: new ContactPage({ - label: "Contact Us", - icon: contactIcon, - group: resourcesGroup, - }), - settings: new SettingsPage({ - label: "Settings", - icon: settingsIcon, - group: "bottom", - }), -}; - -dashboard.pages = pages; - -export { dashboard }; +import { GettingStartedPage } from "./stories/pages/getting-started/GettingStarted"; +import { DocumentationPage } from "./stories/pages/documentation/Documentation"; +import { ContactPage } from "./stories/pages/contact-us/Contact"; +import { GuidedHomePage } from "./stories/pages/guided-mode/GuidedHome"; +import { GuidedNewDatasetPage } from "./stories/pages/guided-mode/setup/GuidedNewDatasetInfo"; +import { GuidedStructurePage } from "./stories/pages/guided-mode/data/GuidedStructure"; +import { sections } from "./stories/pages/globals"; +import { GuidedSubjectsPage } from "./stories/pages/guided-mode/setup/GuidedSubjects"; +import { GuidedSourceDataPage } from "./stories/pages/guided-mode/data/GuidedSourceData"; +import { GuidedMetadataPage } from "./stories/pages/guided-mode/data/GuidedMetadata"; +import { GuidedUploadPage } from "./stories/pages/guided-mode/options/GuidedUpload"; +import { GuidedResultsPage } from "./stories/pages/guided-mode/results/GuidedResults"; +import { Dashboard } from "./stories/Dashboard"; +import { GuidedStubPreviewPage } from "./stories/pages/guided-mode/options/GuidedStubPreview"; +import { GuidedInspectorPage } from "./stories/pages/guided-mode/options/GuidedInspectorPage"; + +import logo from "../assets/img/logo-guide-draft-transparent-tight.png"; +import { GuidedPathExpansionPage } from "./stories/pages/guided-mode/data/GuidedPathExpansion"; +import uploadIcon from "../assets/icons/dandi.svg"; +import inspectIcon from "../assets/icons/inspect.svg?raw"; +import neurosiftIcon from "../assets/icons/neurosift-logo.svg?raw"; + +import settingsIcon from "../assets/icons/settings.svg?raw"; + +import { UploadsPage } from "./stories/pages/uploads/UploadsPage"; +import { SettingsPage } from "./stories/pages/settings/SettingsPage"; +import { InspectPage } from "./stories/pages/inspect/InspectPage"; +import { PreviewPage } from "./stories/pages/preview/PreviewPage"; +import { GuidedPreform } from "./stories/pages/guided-mode/setup/Preform"; +import { GuidedDandiResultsPage } from "./stories/pages/guided-mode/results/GuidedDandiResults"; + +let dashboard = document.querySelector("nwb-dashboard"); +if (!dashboard) dashboard = new Dashboard(); +dashboard.logo = logo; +dashboard.name = "NWB GUIDE"; +dashboard.renderNameInSidebar = false; + +const resourcesGroup = "Resources"; + +const guidedIcon = ` + + + + +`; + +const documentationIcon = ` + + +`; + +const contactIcon = ` + +`; + +const pages = { + "/": new GuidedHomePage({ + label: "Convert", + icon: guidedIcon, + pages: { + details: new GuidedNewDatasetPage({ + title: "Project Setup", + label: "Project details", + section: sections[0], + }), + + workflow: new GuidedPreform({ + title: "Pipeline Workflow", + label: "Pipeline workflow", + section: sections[0], + }), + + structure: new GuidedStructurePage({ + title: "Provide Data Formats", + label: "Data formats", + section: sections[0], + }), + + locate: new GuidedPathExpansionPage({ + title: "Locate Data", + label: "Locate data", + section: sections[0], + }), + + subjects: new GuidedSubjectsPage({ + title: "Subject Metadata", + label: "Subject details", + section: sections[0], + }), + + sourcedata: new GuidedSourceDataPage({ + title: "Source Data Information", + label: "Source data", + section: sections[1], + }), + + metadata: new GuidedMetadataPage({ + title: "File Metadata", + label: "File metadata", + section: sections[1], + }), + + inspect: new GuidedInspectorPage({ + title: "Inspector Report", + label: "Validate metadata", + section: sections[2], + sync: ["preview"], + }), + + preview: new GuidedStubPreviewPage({ + title: "Conversion Preview", + label: "Preview NWB files", + section: sections[2], + sync: ["preview"], + }), + + conversion: new GuidedResultsPage({ + title: "Conversion Review", + label: "Review conversion", + section: sections[2], + sync: ["conversion"], + }), + + upload: new GuidedUploadPage({ + title: "DANDI Upload", + label: "Upload to DANDI", + section: sections[3], + sync: ["conversion"], + }), + + review: new GuidedDandiResultsPage({ + title: "Upload Review", + label: "Review published data", + section: sections[3], + }), + }, + }), + validate: new InspectPage({ + label: "Validate", + icon: inspectIcon, + }), + explore: new PreviewPage({ + label: "Explore", + icon: neurosiftIcon, + }), + uploads: new UploadsPage({ + label: "Upload", + icon: uploadIcon, + }), + docs: new DocumentationPage({ + label: "Documentation", + icon: documentationIcon, + group: resourcesGroup, + }), + contact: new ContactPage({ + label: "Contact Us", + icon: contactIcon, + group: resourcesGroup, + }), + settings: new SettingsPage({ + label: "Settings", + icon: settingsIcon, + group: "bottom", + }), +}; + +dashboard.pages = pages; + +export { dashboard }; From f5893d9af6bedead1440aca0b00da3f90e94ba70 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 28 May 2024 15:35:37 -0700 Subject: [PATCH 5/6] Update InspectorList.stories.js --- stories/components/InspectorList.stories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/components/InspectorList.stories.js b/stories/components/InspectorList.stories.js index 43c41ae7c..8f34aa767 100644 --- a/stories/components/InspectorList.stories.js +++ b/stories/components/InspectorList.stories.js @@ -1,5 +1,5 @@ import { InspectorList } from "../../src/electron/renderer/src/stories/preview/inspector/InspectorList"; -import testInspectorList from "../data/inspector_output.json"; +import testInspectorList from "../inputs/inspector_output.json"; export default { title: "Components/Inspector List", From ac5a8eda77ae08ee23a4a94e57b8268cfe053b3d Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 28 May 2024 15:55:00 -0700 Subject: [PATCH 6/6] Update pages.js --- src/electron/renderer/src/pages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/renderer/src/pages.js b/src/electron/renderer/src/pages.js index 0dc468955..7dbd400e7 100644 --- a/src/electron/renderer/src/pages.js +++ b/src/electron/renderer/src/pages.js @@ -16,7 +16,7 @@ import { GuidedInspectorPage } from "./stories/pages/guided-mode/options/GuidedI import logo from "../assets/img/logo-guide-draft-transparent-tight.png"; import { GuidedPathExpansionPage } from "./stories/pages/guided-mode/data/GuidedPathExpansion"; -import uploadIcon from "../assets/icons/dandi.svg"; +import uploadIcon from "../assets/icons/dandi.svg?raw"; import inspectIcon from "../assets/icons/inspect.svg?raw"; import neurosiftIcon from "../assets/icons/neurosift-logo.svg?raw";