From 867279b9cc86f19c67d1751db756144f1a1bee8e Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 5 Sep 2023 16:01:00 -0700 Subject: [PATCH 01/12] Create draft of standalone preview page --- pyflask/app.py | 27 ++++++++- src/renderer/src/pages.js | 15 +++-- src/renderer/src/stories/assets/inspect.svg | 2 +- src/renderer/src/stories/assets/preview.svg | 7 +++ .../pages/guided-mode/options/utils.js | 4 +- .../src/stories/pages/preview/PreviewPage.js | 55 +++++++++++++++++++ src/renderer/src/stories/preview/Neurosift.js | 14 +++-- 7 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 src/renderer/src/stories/assets/preview.svg create mode 100644 src/renderer/src/stories/pages/preview/PreviewPage.js diff --git a/pyflask/app.py b/pyflask/app.py index a87618798..da0205fdf 100644 --- a/pyflask/app.py +++ b/pyflask/app.py @@ -2,15 +2,18 @@ import sys import json import multiprocessing +from os.path import sep from logging import Formatter, DEBUG from logging.handlers import RotatingFileHandler from pathlib import Path +from urllib.parse import unquote + # https://stackoverflow.com/questions/32672596/pyinstaller-loads-script-multiple-times#comment103216434_32677108 multiprocessing.freeze_support() -from flask import Flask, request, send_from_directory +from flask import Flask, request, send_from_directory, send_file from flask_cors import CORS from flask_restx import Api, Resource @@ -52,6 +55,28 @@ api.add_namespace(neuroconv_api) api.init_app(app) +registered = {} + +@app.route("/files") +def get_all_files(): + return list(registered.keys()) + +@app.route("/files/", methods=["GET", "POST"]) +def handle_file_request(path): + + + if request.method == 'GET': + if registered[path]: + return send_file(f"{sep}{unquote(path)}") + else: + app.abort(404, 'Resource is not accessible.') + + else: + if ('.nwb' in path): + registered[path] = True + return request.base_url + else: + app.abort(400, str('Path does not point to an NWB file.')) @app.route("/conversions/") def send_conversions(path): diff --git a/src/renderer/src/pages.js b/src/renderer/src/pages.js index 09ba41811..d53784f4d 100644 --- a/src/renderer/src/pages.js +++ b/src/renderer/src/pages.js @@ -22,11 +22,14 @@ import { TutorialPage } from "./stories/pages/tutorial/Tutorial"; import tutorialIcon from "./stories/assets/exploration.svg?raw"; import uploadIcon from "./stories/assets/dandi.svg?raw"; import inspectIcon from "./stories/assets/inspect.svg?raw"; +import previewIcon from "./stories/assets/preview.svg?raw"; + import settingsIcon from "./stories/assets/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"; let dashboard = document.querySelector("nwb-dashboard"); if (!dashboard) dashboard = new Dashboard(); @@ -165,14 +168,18 @@ const pages = { }), }, }), - uploads: new UploadsPage({ - label: "Uploads", - icon: uploadIcon, - }), inspect: new InspectPage({ label: "Inspect", icon: inspectIcon, }), + preview: new PreviewPage({ + label: "Preview", + icon: previewIcon, + }), + uploads: new UploadsPage({ + label: "Uploads", + icon: uploadIcon, + }), tutorial: new TutorialPage({ label: "Tutorial", icon: tutorialIcon, diff --git a/src/renderer/src/stories/assets/inspect.svg b/src/renderer/src/stories/assets/inspect.svg index dbbbd8f77..a94da7c42 100644 --- a/src/renderer/src/stories/assets/inspect.svg +++ b/src/renderer/src/stories/assets/inspect.svg @@ -4,4 +4,4 @@ height="25" viewBox="0 -960 960 960" width="25" style="margin-right: 30px; margin-bottom: -5px" -> \ No newline at end of file +> \ No newline at end of file diff --git a/src/renderer/src/stories/assets/preview.svg b/src/renderer/src/stories/assets/preview.svg new file mode 100644 index 000000000..dbbbd8f77 --- /dev/null +++ b/src/renderer/src/stories/assets/preview.svg @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/src/renderer/src/stories/pages/guided-mode/options/utils.js b/src/renderer/src/stories/pages/guided-mode/options/utils.js index 6d1d0020e..20785b4f2 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/utils.js +++ b/src/renderer/src/stories/pages/guided-mode/options/utils.js @@ -24,7 +24,9 @@ export const run = async (url, payload, options = {}) => { const needsSwal = !options.swal && options.swal !== false; if (needsSwal) openProgressSwal(options).then((swal) => (options.onOpen ? options.onOpen(swal) : undefined)); - const results = await fetch(`${baseUrl}/neuroconv/${url}`, { + if (!('base' in options)) options.base = '/neuroconv' + + const results = await fetch(`${baseUrl}${options.base || ''}/${url}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), diff --git a/src/renderer/src/stories/pages/preview/PreviewPage.js b/src/renderer/src/stories/pages/preview/PreviewPage.js new file mode 100644 index 000000000..5f2b276c5 --- /dev/null +++ b/src/renderer/src/stories/pages/preview/PreviewPage.js @@ -0,0 +1,55 @@ +import { html } from "lit"; +import { Page } from "../Page.js"; +import { onThrow } from "../../../errors"; +import { Button } from "../../Button.js"; + +import { run } from "../guided-mode/options/utils.js"; +import { JSONSchemaInput } from "../../JSONSchemaInput.js"; +import { Modal } from "../../Modal"; +import { truncateFilePaths } from "../../preview/NWBFilePreview.js"; +import { InspectorList } from "../../preview/inspector/InspectorList.js"; +import { Neurosift } from "../../preview/Neurosift.js"; +import { baseUrl } from "../../../globals.js"; + + +export class PreviewPage extends Page { + constructor(...args) { + super(...args); + } + + render() { + + const neurosift = new Neurosift() + + this.input = new JSONSchemaInput({ + path: ['file_path'], + info: { + type: 'string', + format: 'file' + }, + onUpdate: async (path) => { + + const result = await fetch(`${baseUrl}/files/${path}`, { method: "POST" }).then((res) => res.text()); + + if (result) neurosift.url = result + }, + onThrow, + }); + + return html` +
+
+
+

NWB File Preview

+
+

Use Neurosift to preview of your NWB file.

+
+
+ ${this.input} + ${neurosift} +
+ `; + } +} + +customElements.get("nwbguide-preview-page") || customElements.define("nwbguide-preview-page", PreviewPage); diff --git a/src/renderer/src/stories/preview/Neurosift.js b/src/renderer/src/stories/preview/Neurosift.js index 5c8b81d8a..eee813c05 100644 --- a/src/renderer/src/stories/preview/Neurosift.js +++ b/src/renderer/src/stories/preview/Neurosift.js @@ -26,7 +26,7 @@ export class Neurosift extends LitElement { height: 100%; } - div { + .loader-container { display: flex; align-items: center; justify-content: center; @@ -49,20 +49,26 @@ export class Neurosift extends LitElement { `; } - constructor({ url }) { + static get properties() { + return { + url: {type: String, reflect: true} + } + } + + constructor({ url } = {}) { super(); this.url = url; } render() { - return html`
${new Loader({ message: "Loading Neurosift view..." })}
+ return this.url ? html`
${new Loader({ message: "Loading Neurosift view..." })}
`; + >` : ``; } } From b0163228435e3220e648f401334d551627b19585 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 5 Sep 2023 18:25:55 -0700 Subject: [PATCH 02/12] Support previewing NWB files when selected in native file explorer --- package.json | 2 +- src/renderer/src/electron/index.js | 9 ++- src/renderer/src/globals.js | 14 ++++ src/renderer/src/progress/index.js | 4 +- src/renderer/src/progress/update.js | 17 +---- src/renderer/src/stories/Dashboard.js | 4 ++ .../src/stories/pages/inspect/InspectPage.js | 72 ++++++++++--------- .../src/stories/pages/preview/PreviewPage.js | 45 ++++++------ 8 files changed, 92 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index bad2cfe3e..59c58571f 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ { "ext": "nwb", "name": "NWB File", - "role": "Editor" + "role": "Viewer" } ], "files": [ diff --git a/src/renderer/src/electron/index.js b/src/renderer/src/electron/index.js index 4f9b30865..537ee2dd3 100644 --- a/src/renderer/src/electron/index.js +++ b/src/renderer/src/electron/index.js @@ -1,3 +1,4 @@ +import { updateURLParams } from "../globals.js"; import isElectron from "./check.js"; export { isElectron }; @@ -21,8 +22,12 @@ if (isElectron) { remote = require("@electron/remote"); app = remote.app; - electron.ipcRenderer.on("fileOpened", (info, ...args) => { - console.log("File opened!", ...args); + electron.ipcRenderer.on("fileOpened", (info, filepath) => { + updateURLParams({ file: filepath }) + const dashboard = document.querySelector("nwb-dashboard"); + const activePage = dashboard.getAttribute('activePage') + if (activePage === 'preview') dashboard.requestUpdate() + else dashboard.setAttribute("activePage", 'preview') }); ["log", "warn", "error"].forEach((method) => diff --git a/src/renderer/src/globals.js b/src/renderer/src/globals.js index b235577dd..1cb3ad5bf 100644 --- a/src/renderer/src/globals.js +++ b/src/renderer/src/globals.js @@ -13,3 +13,17 @@ export let runOnLoad = (fn) => { export const baseUrl = `http://127.0.0.1:${port}`; export const supportedInterfaces = guideGlobalMetadata.supported_interfaces; + +export function updateURLParams(paramsToUpdate) { + const params = new URLSearchParams(location.search); + for (let key in paramsToUpdate) { + const value = paramsToUpdate[key]; + if (value == undefined) params.delete(key); + else params.set(key, value); + } + + // Update browser history state + const value = `${location.pathname}?${params}`; + if (history.state) Object.assign(history.state, paramsToUpdate); + window.history.pushState(history.state, null, value); +} diff --git a/src/renderer/src/progress/index.js b/src/renderer/src/progress/index.js index b5c7c2121..a2aa06dfe 100644 --- a/src/renderer/src/progress/index.js +++ b/src/renderer/src/progress/index.js @@ -2,9 +2,9 @@ import Swal from "sweetalert2"; import { guidedProgressFilePath, reloadPageToHome, isStorybook, appDirectory } from "../dependencies/simple.js"; import { fs } from "../electron/index.js"; -import { joinPath, runOnLoad } from "../globals.js"; +import { joinPath, runOnLoad, updateURLParams } from "../globals.js"; import { merge } from "../stories/pages/utils.js"; -import { updateAppProgress, updateFile, updateURLParams } from "./update.js"; +import { updateAppProgress, updateFile } from "./update.js"; export * from "./update"; diff --git a/src/renderer/src/progress/update.js b/src/renderer/src/progress/update.js index 06b422639..5c4f72aea 100644 --- a/src/renderer/src/progress/update.js +++ b/src/renderer/src/progress/update.js @@ -1,6 +1,6 @@ import { guidedProgressFilePath } from "../dependencies/simple.js"; import { fs } from "../electron/index.js"; -import { joinPath } from "../globals.js"; +import { joinPath, updateURLParams } from "../globals.js"; import { get } from "./index.js"; export const update = (newDatasetName, previousDatasetName) => { @@ -23,21 +23,6 @@ export const update = (newDatasetName, previousDatasetName) => { return "Dataset name updated"; } else throw new Error("No previous dataset name provided"); }; - -export function updateURLParams(paramsToUpdate) { - const params = new URLSearchParams(location.search); - for (let key in paramsToUpdate) { - const value = paramsToUpdate[key]; - if (value == undefined) params.delete(key); - else params.set(key, value); - } - - // Update browser history state - const value = `${location.pathname}?${params}`; - if (history.state) Object.assign(history.state, paramsToUpdate); - window.history.pushState(history.state, null, value); -} - export const updateAppProgress = ( pageId, dataOrProjectName = {}, diff --git a/src/renderer/src/stories/Dashboard.js b/src/renderer/src/stories/Dashboard.js index 3d3240331..1c6971117 100644 --- a/src/renderer/src/stories/Dashboard.js +++ b/src/renderer/src/stories/Dashboard.js @@ -133,6 +133,10 @@ export class Dashboard extends LitElement { this.#updated(); } + requestPageUpdate() { + if (this.#active) this.#active.requestUpdate() + } + createRenderRoot() { return this; } diff --git a/src/renderer/src/stories/pages/inspect/InspectPage.js b/src/renderer/src/stories/pages/inspect/InspectPage.js index 5645f77f5..aeceb80fc 100644 --- a/src/renderer/src/stories/pages/inspect/InspectPage.js +++ b/src/renderer/src/stories/pages/inspect/InspectPage.js @@ -15,44 +15,52 @@ export class InspectPage extends Page { super(...args); } + showReport = async (value) => { + if (!value) { + const message = "Please provide a folder to inspect." + onThrow(message) + throw new Error(message) + } + + const items = truncateFilePaths(await run("inspect_folder", { path: value }, { title: "Inspecting your files" }).catch((e) => { + this.notify(e.message, "error"); + throw e; + }), value); + + const list = new InspectorList({ items }); + list.style.padding = "25px"; + + const modal = new Modal({ + header: value + }) + modal.append(list) + document.body.append(modal) + + modal.toggle(true) + } + + input = new JSONSchemaInput({ + path: ['folder_path'], + info: { + type: 'string', + format: 'directory' + }, + onThrow, + }); + render() { const button = new Button({ label: "Inspect Files", - onClick: async () => { - const { value } = this.input - if (!value) { - const message = "Please provide a folder to inspect." - onThrow(message) - throw new Error(message) - } - - const items = truncateFilePaths(await run("inspect_folder", { path: value }, { title: "Inspecting your files" }).catch((e) => { - this.notify(e.message, "error"); - throw e; - }), value); - - const list = new InspectorList({ items }); - list.style.padding = "25px"; - - const modal = new Modal({ - header: value - }) - modal.append(list) - document.body.append(modal) - - modal.toggle(true) - }, + onClick: async () => this.showReport(this.input.value), }); - this.input = new JSONSchemaInput({ - path: ['folder_path'], - info: { - type: 'string', - format: 'directory' - }, - onThrow, - }); + const urlFilePath = new URL(document.location).searchParams.get('file') + + if (urlFilePath) { + this.showReport(urlFilePath) + this.input.value = urlFilePath + } return html`
diff --git a/src/renderer/src/stories/pages/preview/PreviewPage.js b/src/renderer/src/stories/pages/preview/PreviewPage.js index 5f2b276c5..5f9d07214 100644 --- a/src/renderer/src/stories/pages/preview/PreviewPage.js +++ b/src/renderer/src/stories/pages/preview/PreviewPage.js @@ -1,13 +1,7 @@ import { html } from "lit"; import { Page } from "../Page.js"; import { onThrow } from "../../../errors"; -import { Button } from "../../Button.js"; - -import { run } from "../guided-mode/options/utils.js"; import { JSONSchemaInput } from "../../JSONSchemaInput.js"; -import { Modal } from "../../Modal"; -import { truncateFilePaths } from "../../preview/NWBFilePreview.js"; -import { InspectorList } from "../../preview/inspector/InspectorList.js"; import { Neurosift } from "../../preview/Neurosift.js"; import { baseUrl } from "../../../globals.js"; @@ -17,24 +11,31 @@ export class PreviewPage extends Page { super(...args); } - render() { + updatePath = async (path) => { + const result = await fetch(`${baseUrl}/files/${path}`, { method: "POST" }).then((res) => res.text()); + if (result) this.neurosift.url = result + } - const neurosift = new Neurosift() + neurosift = new Neurosift() + + input = new JSONSchemaInput({ + path: ['file_path'], + info: { + type: 'string', + format: 'file' + }, + onUpdate: this.updatePath, + onThrow, + }); + + render() { - this.input = new JSONSchemaInput({ - path: ['file_path'], - info: { - type: 'string', - format: 'file' - }, - onUpdate: async (path) => { + const urlFilePath = new URL(document.location).searchParams.get('file') - const result = await fetch(`${baseUrl}/files/${path}`, { method: "POST" }).then((res) => res.text()); - - if (result) neurosift.url = result - }, - onThrow, - }); + if (urlFilePath) { + this.updatePath(urlFilePath) + this.input.value = urlFilePath + } return html`
@@ -46,7 +47,7 @@ export class PreviewPage extends Page {
${this.input} - ${neurosift} + ${this.neurosift}
`; } From b9129faab99b8402d00836e01edbf44a2ec63f2c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 01:26:46 +0000 Subject: [PATCH 03/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyflask/app.py | 13 +++--- src/renderer/src/electron/index.js | 8 ++-- src/renderer/src/stories/Dashboard.js | 2 +- src/renderer/src/stories/assets/inspect.svg | 10 ++--- src/renderer/src/stories/assets/preview.svg | 10 ++--- .../pages/guided-mode/options/utils.js | 4 +- .../src/stories/pages/inspect/InspectPage.js | 43 ++++++++++--------- .../src/stories/pages/preview/PreviewPage.js | 23 +++++----- src/renderer/src/stories/preview/Neurosift.js | 24 ++++++----- .../preview/inspector/InspectorList.js | 10 ++--- 10 files changed, 74 insertions(+), 73 deletions(-) diff --git a/pyflask/app.py b/pyflask/app.py index da0205fdf..8a978e0d0 100644 --- a/pyflask/app.py +++ b/pyflask/app.py @@ -57,26 +57,27 @@ registered = {} + @app.route("/files") def get_all_files(): return list(registered.keys()) + @app.route("/files/", methods=["GET", "POST"]) def handle_file_request(path): - - - if request.method == 'GET': + if request.method == "GET": if registered[path]: return send_file(f"{sep}{unquote(path)}") else: - app.abort(404, 'Resource is not accessible.') + app.abort(404, "Resource is not accessible.") else: - if ('.nwb' in path): + if ".nwb" in path: registered[path] = True return request.base_url else: - app.abort(400, str('Path does not point to an NWB file.')) + app.abort(400, str("Path does not point to an NWB file.")) + @app.route("/conversions/") def send_conversions(path): diff --git a/src/renderer/src/electron/index.js b/src/renderer/src/electron/index.js index 537ee2dd3..49545692f 100644 --- a/src/renderer/src/electron/index.js +++ b/src/renderer/src/electron/index.js @@ -23,11 +23,11 @@ if (isElectron) { app = remote.app; electron.ipcRenderer.on("fileOpened", (info, filepath) => { - updateURLParams({ file: filepath }) + updateURLParams({ file: filepath }); const dashboard = document.querySelector("nwb-dashboard"); - const activePage = dashboard.getAttribute('activePage') - if (activePage === 'preview') dashboard.requestUpdate() - else dashboard.setAttribute("activePage", 'preview') + const activePage = dashboard.getAttribute("activePage"); + if (activePage === "preview") dashboard.requestUpdate(); + else dashboard.setAttribute("activePage", "preview"); }); ["log", "warn", "error"].forEach((method) => diff --git a/src/renderer/src/stories/Dashboard.js b/src/renderer/src/stories/Dashboard.js index 1c6971117..121391965 100644 --- a/src/renderer/src/stories/Dashboard.js +++ b/src/renderer/src/stories/Dashboard.js @@ -134,7 +134,7 @@ export class Dashboard extends LitElement { } requestPageUpdate() { - if (this.#active) this.#active.requestUpdate() + if (this.#active) this.#active.requestUpdate(); } createRenderRoot() { diff --git a/src/renderer/src/stories/assets/inspect.svg b/src/renderer/src/stories/assets/inspect.svg index a94da7c42..8b59b651e 100644 --- a/src/renderer/src/stories/assets/inspect.svg +++ b/src/renderer/src/stories/assets/inspect.svg @@ -1,7 +1,7 @@ - \ No newline at end of file +> diff --git a/src/renderer/src/stories/assets/preview.svg b/src/renderer/src/stories/assets/preview.svg index dbbbd8f77..467b1d186 100644 --- a/src/renderer/src/stories/assets/preview.svg +++ b/src/renderer/src/stories/assets/preview.svg @@ -1,7 +1,7 @@ - \ No newline at end of file +> diff --git a/src/renderer/src/stories/pages/guided-mode/options/utils.js b/src/renderer/src/stories/pages/guided-mode/options/utils.js index 20785b4f2..48031dd06 100644 --- a/src/renderer/src/stories/pages/guided-mode/options/utils.js +++ b/src/renderer/src/stories/pages/guided-mode/options/utils.js @@ -24,9 +24,9 @@ export const run = async (url, payload, options = {}) => { const needsSwal = !options.swal && options.swal !== false; if (needsSwal) openProgressSwal(options).then((swal) => (options.onOpen ? options.onOpen(swal) : undefined)); - if (!('base' in options)) options.base = '/neuroconv' + if (!("base" in options)) options.base = "/neuroconv"; - const results = await fetch(`${baseUrl}${options.base || ''}/${url}`, { + const results = await fetch(`${baseUrl}${options.base || ""}/${url}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), diff --git a/src/renderer/src/stories/pages/inspect/InspectPage.js b/src/renderer/src/stories/pages/inspect/InspectPage.js index aeceb80fc..5b5164a2c 100644 --- a/src/renderer/src/stories/pages/inspect/InspectPage.js +++ b/src/renderer/src/stories/pages/inspect/InspectPage.js @@ -9,7 +9,6 @@ import { Modal } from "../../Modal"; import { truncateFilePaths } from "../../preview/NWBFilePreview.js"; import { InspectorList } from "../../preview/inspector/InspectorList.js"; - export class InspectPage extends Page { constructor(...args) { super(...args); @@ -17,49 +16,51 @@ export class InspectPage extends Page { showReport = async (value) => { if (!value) { - const message = "Please provide a folder to inspect." - onThrow(message) - throw new Error(message) + const message = "Please provide a folder to inspect."; + onThrow(message); + throw new Error(message); } - const items = truncateFilePaths(await run("inspect_folder", { path: value }, { title: "Inspecting your files" }).catch((e) => { - this.notify(e.message, "error"); - throw e; - }), value); + const items = truncateFilePaths( + await run("inspect_folder", { path: value }, { title: "Inspecting your files" }).catch((e) => { + this.notify(e.message, "error"); + throw e; + }), + value + ); const list = new InspectorList({ items }); list.style.padding = "25px"; const modal = new Modal({ - header: value - }) - modal.append(list) - document.body.append(modal) + header: value, + }); + modal.append(list); + document.body.append(modal); - modal.toggle(true) - } + modal.toggle(true); + }; input = new JSONSchemaInput({ - path: ['folder_path'], + path: ["folder_path"], info: { - type: 'string', - format: 'directory' + type: "string", + format: "directory", }, onThrow, }); render() { - const button = new Button({ label: "Inspect Files", onClick: async () => this.showReport(this.input.value), }); - const urlFilePath = new URL(document.location).searchParams.get('file') + const urlFilePath = new URL(document.location).searchParams.get("file"); if (urlFilePath) { - this.showReport(urlFilePath) - this.input.value = urlFilePath + this.showReport(urlFilePath); + this.input.value = urlFilePath; } return html` diff --git a/src/renderer/src/stories/pages/preview/PreviewPage.js b/src/renderer/src/stories/pages/preview/PreviewPage.js index 5f9d07214..92acdf666 100644 --- a/src/renderer/src/stories/pages/preview/PreviewPage.js +++ b/src/renderer/src/stories/pages/preview/PreviewPage.js @@ -5,7 +5,6 @@ import { JSONSchemaInput } from "../../JSONSchemaInput.js"; import { Neurosift } from "../../preview/Neurosift.js"; import { baseUrl } from "../../../globals.js"; - export class PreviewPage extends Page { constructor(...args) { super(...args); @@ -13,28 +12,27 @@ export class PreviewPage extends Page { updatePath = async (path) => { const result = await fetch(`${baseUrl}/files/${path}`, { method: "POST" }).then((res) => res.text()); - if (result) this.neurosift.url = result - } + if (result) this.neurosift.url = result; + }; - neurosift = new Neurosift() + neurosift = new Neurosift(); input = new JSONSchemaInput({ - path: ['file_path'], + path: ["file_path"], info: { - type: 'string', - format: 'file' + type: "string", + format: "file", }, onUpdate: this.updatePath, onThrow, }); render() { - - const urlFilePath = new URL(document.location).searchParams.get('file') + const urlFilePath = new URL(document.location).searchParams.get("file"); if (urlFilePath) { - this.updatePath(urlFilePath) - this.input.value = urlFilePath + this.updatePath(urlFilePath); + this.input.value = urlFilePath; } return html` @@ -46,8 +44,7 @@ export class PreviewPage extends Page {

Use Neurosift to preview of your NWB file.


- ${this.input} - ${this.neurosift} + ${this.input} ${this.neurosift} `; } diff --git a/src/renderer/src/stories/preview/Neurosift.js b/src/renderer/src/stories/preview/Neurosift.js index eee813c05..41cbff439 100644 --- a/src/renderer/src/stories/preview/Neurosift.js +++ b/src/renderer/src/stories/preview/Neurosift.js @@ -50,9 +50,9 @@ export class Neurosift extends LitElement { } static get properties() { - return { - url: {type: String, reflect: true} - } + return { + url: { type: String, reflect: true }, + }; } constructor({ url } = {}) { @@ -61,14 +61,16 @@ export class Neurosift extends LitElement { } render() { - return this.url ? html`
${new Loader({ message: "Loading Neurosift view..." })}
- ` : ``; + return this.url + ? html`
${new Loader({ message: "Loading Neurosift view..." })}
+ ` + : ``; } } diff --git a/src/renderer/src/stories/preview/inspector/InspectorList.js b/src/renderer/src/stories/preview/inspector/InspectorList.js index ec5bb3bc0..b4fedb670 100644 --- a/src/renderer/src/stories/preview/inspector/InspectorList.js +++ b/src/renderer/src/stories/preview/inspector/InspectorList.js @@ -29,14 +29,15 @@ const aggregateMessages = (items) => { }; export class InspectorList extends List { - static get styles() { - - return [super.styles, css` + return [ + super.styles, + css` :host { display: block; } - }`] + }`, + ]; } constructor({ items, listStyles }) { @@ -64,7 +65,6 @@ customElements.get("inspector-list") || customElements.define("inspector-list", export class InspectorListItem extends LitElement { static get styles() { return css` - :host { display: block; background: gainsboro; From ee41639327cbb772941c355c9dd54eb0af01e729 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 13 Sep 2023 07:58:39 -0700 Subject: [PATCH 04/12] Update logo, name, and backend path usage for Windows --- pyflask/app.py | 2 +- src/renderer/src/pages.js | 6 ++-- .../src/stories/assets/neurosift-logo.svg | 34 +++++++++++++++++++ .../src/stories/pages/preview/PreviewPage.js | 4 +-- 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 src/renderer/src/stories/assets/neurosift-logo.svg diff --git a/pyflask/app.py b/pyflask/app.py index 8a978e0d0..890d9bede 100644 --- a/pyflask/app.py +++ b/pyflask/app.py @@ -67,7 +67,7 @@ def get_all_files(): def handle_file_request(path): if request.method == "GET": if registered[path]: - return send_file(f"{sep}{unquote(path)}") + return send_file(unquote(path)) else: app.abort(404, "Resource is not accessible.") diff --git a/src/renderer/src/pages.js b/src/renderer/src/pages.js index d53784f4d..a02bed0c8 100644 --- a/src/renderer/src/pages.js +++ b/src/renderer/src/pages.js @@ -22,7 +22,7 @@ import { TutorialPage } from "./stories/pages/tutorial/Tutorial"; import tutorialIcon from "./stories/assets/exploration.svg?raw"; import uploadIcon from "./stories/assets/dandi.svg?raw"; import inspectIcon from "./stories/assets/inspect.svg?raw"; -import previewIcon from "./stories/assets/preview.svg?raw"; +import neurosiftIcon from "./stories/assets/neurosift-logo.svg?raw"; import settingsIcon from "./stories/assets/settings.svg?raw"; @@ -173,8 +173,8 @@ const pages = { icon: inspectIcon, }), preview: new PreviewPage({ - label: "Preview", - icon: previewIcon, + label: "Neurosift", + icon: neurosiftIcon, }), uploads: new UploadsPage({ label: "Uploads", diff --git a/src/renderer/src/stories/assets/neurosift-logo.svg b/src/renderer/src/stories/assets/neurosift-logo.svg new file mode 100644 index 000000000..94a564956 --- /dev/null +++ b/src/renderer/src/stories/assets/neurosift-logo.svg @@ -0,0 +1,34 @@ + + + + +Created by potrace 1.16, written by Peter Selinger 2001-2019 + + + + + + diff --git a/src/renderer/src/stories/pages/preview/PreviewPage.js b/src/renderer/src/stories/pages/preview/PreviewPage.js index 92acdf666..e889c48e9 100644 --- a/src/renderer/src/stories/pages/preview/PreviewPage.js +++ b/src/renderer/src/stories/pages/preview/PreviewPage.js @@ -39,9 +39,9 @@ export class PreviewPage extends Page {
-

NWB File Preview

+

Neurosift File Visualization

-

Use Neurosift to preview of your NWB file.

+

Explore your NWB file using Neurosift


${this.input} ${this.neurosift} From eb795ac7cd07ba9f250205563ae7edf7d70e5f21 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:00:48 +0000 Subject: [PATCH 05/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/stories/assets/neurosift-logo.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/stories/assets/neurosift-logo.svg b/src/renderer/src/stories/assets/neurosift-logo.svg index 94a564956..460120d59 100644 --- a/src/renderer/src/stories/assets/neurosift-logo.svg +++ b/src/renderer/src/stories/assets/neurosift-logo.svg @@ -1,8 +1,8 @@ - Date: Wed, 13 Sep 2023 08:09:27 -0700 Subject: [PATCH 06/12] Catch windows file-open bug --- src/renderer/src/electron/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/electron/index.js b/src/renderer/src/electron/index.js index 49545692f..ece0741c1 100644 --- a/src/renderer/src/electron/index.js +++ b/src/renderer/src/electron/index.js @@ -23,6 +23,7 @@ if (isElectron) { app = remote.app; electron.ipcRenderer.on("fileOpened", (info, filepath) => { + if (filepath === '.') return // BUG: Windows throws . when the application is opened (at least dev mode)... updateURLParams({ file: filepath }); const dashboard = document.querySelector("nwb-dashboard"); const activePage = dashboard.getAttribute("activePage"); From 33b5cd6d8d807527568c581478e025b2242bad75 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:10:26 +0000 Subject: [PATCH 07/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/renderer/src/electron/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/electron/index.js b/src/renderer/src/electron/index.js index ece0741c1..53f07f615 100644 --- a/src/renderer/src/electron/index.js +++ b/src/renderer/src/electron/index.js @@ -23,7 +23,7 @@ if (isElectron) { app = remote.app; electron.ipcRenderer.on("fileOpened", (info, filepath) => { - if (filepath === '.') return // BUG: Windows throws . when the application is opened (at least dev mode)... + if (filepath === ".") return; // BUG: Windows throws . when the application is opened (at least dev mode)... updateURLParams({ file: filepath }); const dashboard = document.querySelector("nwb-dashboard"); const activePage = dashboard.getAttribute("activePage"); From ad0554bb024efb84f4dffb88dd43d27cfdf3b484 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn <46533749+GarrettMFlynn@users.noreply.github.com> Date: Wed, 13 Sep 2023 08:16:48 -0700 Subject: [PATCH 08/12] Fix backend cause of windows issue --- src/main/main.ts | 10 +++++++--- src/renderer/src/electron/index.js | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/main.ts b/src/main/main.ts index da68ea02b..39001a2e4 100755 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -355,14 +355,18 @@ function initialize() { else app.on("ready", onAppReady) } -function onFileOpened(_, path: string) { +function isValidFile(filepath: string) { + return !fs.existsSync(filepath) && path.extname(filepath) === '.nwb' +} + +function onFileOpened(_, filepath: string) { restoreWindow() || initialize(); // Ensure the application is properly visible - onWindowReady((win) => win.webContents.send('fileOpened', path)) + onWindowReady((win) => win.webContents.send('fileOpened', filepath)) } if (isWindows && process.argv.length >= 2) { const openFilePath = process.argv[1]; - if (openFilePath !== "") onFileOpened(null, openFilePath) + if (isValidFile(openFilePath)) onFileOpened(null, openFilePath) } // Make this app a single instance app. diff --git a/src/renderer/src/electron/index.js b/src/renderer/src/electron/index.js index ece0741c1..49545692f 100644 --- a/src/renderer/src/electron/index.js +++ b/src/renderer/src/electron/index.js @@ -23,7 +23,6 @@ if (isElectron) { app = remote.app; electron.ipcRenderer.on("fileOpened", (info, filepath) => { - if (filepath === '.') return // BUG: Windows throws . when the application is opened (at least dev mode)... updateURLParams({ file: filepath }); const dashboard = document.querySelector("nwb-dashboard"); const activePage = dashboard.getAttribute("activePage"); From b6993325ea4035167dc721a52723beee4ffa87c6 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 13 Sep 2023 08:25:34 -0700 Subject: [PATCH 09/12] Add description to neurosift loader --- src/renderer/src/stories/JSONSchemaForm.js | 15 +----------- src/renderer/src/stories/JSONSchemaInput.js | 24 +++++++++++++++++++ src/renderer/src/stories/Loader.ts | 3 ++- .../src/stories/pages/preview/PreviewPage.js | 1 + src/renderer/src/stories/preview/Neurosift.js | 2 +- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index f16987160..3fc1d1219 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -2,7 +2,7 @@ import { LitElement, css, html } from "lit"; import { Accordion } from "./Accordion"; import { checkStatus } from "../validation"; -import { capitalize, header } from "./forms/utils"; +import { header } from "./forms/utils"; import { resolve } from "../promises"; import { merge } from "./pages/utils"; import { resolveProperties } from "./pages/guided-mode/data/utils"; @@ -112,14 +112,6 @@ pre { .required.conditional label:after { color: transparent; } - - - .guided--text-input-instructions { - font-size: 13px; - width: 100%; - padding-top: 4px; - color: dimgray !important; - } `; document.addEventListener("dragover", (e) => { @@ -422,11 +414,6 @@ export class JSONSchemaForm extends LitElement { > ${interactiveInput} - ${info.description - ? html`

- ${capitalize(info.description)}${info.description.slice(-1)[0] === "." ? "" : "."} -

` - : ""}
diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index 5d13ea390..fe231617c 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -8,6 +8,8 @@ import { Button } from "./Button"; import { List } from "./List"; import { Modal } from "./Modal"; +import { capitalize } from "./forms/utils"; + const filesystemQueries = ["file", "directory"]; export class JSONSchemaInput extends LitElement { @@ -69,6 +71,13 @@ export class JSONSchemaInput extends LitElement { input[type="number"].hideStep { -moz-appearance: textfield; } + + .guided--text-input-instructions { + font-size: 13px; + width: 100%; + padding-top: 4px; + color: dimgray !important; + } `; } @@ -118,6 +127,21 @@ export class JSONSchemaInput extends LitElement { } render() { + + const { info } = this; + + const input = this.#render() + return html` + ${input} + ${info.description + ? html`

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

` + : ""} + ` + } + + #render() { const { validateOnChange, info, path: fullPath } = this; const path = typeof fullPath === "string" ? fullPath.split("-") : [...fullPath]; diff --git a/src/renderer/src/stories/Loader.ts b/src/renderer/src/stories/Loader.ts index 97e3febf9..ef52cfe30 100644 --- a/src/renderer/src/stories/Loader.ts +++ b/src/renderer/src/stories/Loader.ts @@ -1,4 +1,5 @@ import { LitElement, css, html } from "lit"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; // From https://loading.io/css/ @@ -120,7 +121,7 @@ export class Loader extends LitElement { return html`
- ${this.message ? html`${this.message}` : ''} + ${this.message ? html`${unsafeHTML(this.message)}` : ''}
` } diff --git a/src/renderer/src/stories/pages/preview/PreviewPage.js b/src/renderer/src/stories/pages/preview/PreviewPage.js index e889c48e9..c07d0c01d 100644 --- a/src/renderer/src/stories/pages/preview/PreviewPage.js +++ b/src/renderer/src/stories/pages/preview/PreviewPage.js @@ -22,6 +22,7 @@ export class PreviewPage extends Page { info: { type: "string", format: "file", + description: "Please provide a file path that you'd like to visualize using Neurosift. The NWB GUIDE will serve this file and access the appropriate URL automatically." }, onUpdate: this.updatePath, onThrow, diff --git a/src/renderer/src/stories/preview/Neurosift.js b/src/renderer/src/stories/preview/Neurosift.js index 41cbff439..14dd499ec 100644 --- a/src/renderer/src/stories/preview/Neurosift.js +++ b/src/renderer/src/stories/preview/Neurosift.js @@ -62,7 +62,7 @@ export class Neurosift extends LitElement { render() { return this.url - ? html`
${new Loader({ message: "Loading Neurosift view..." })}
+ ? html`
${new Loader({ message: `Loading Neurosift view...
${this.url}` })}