Skip to content

Commit

Permalink
Add drag and drop support for neuroglancer json files.
Browse files Browse the repository at this point in the history
  • Loading branch information
We-Gold committed Aug 7, 2024
1 parent dc6ff70 commit 797cb45
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@ import {
newFolder,
renameFSItem,
isPathFile,
directory
directory,
readFile
} from '@renderer/interfaces/file'
import { parseNeuroglancerJSON } from '@renderer/schemas/neuroglancer-json-schema'
import { IFrameContext } from '@renderer/contexts/IFrameContext'
import { SendNeuroglancerJSON } from '@renderer/schemas/iframe-message-schema'

function FileExplorer(): JSX.Element {
const { clearDragEvent, parentChildData, active } = useContext(DragContext)
const { nodes, directoryName, directoryPath, setDirectory } = useContext(DirectoryContext)
const { broadcast } = useContext(IFrameContext)
const { point, clicked, data, handleContextMenu } = useContextMenu<FileSystemNode>()

const [renamePath, setRenamePath] = useState<string | null>(null)
Expand Down Expand Up @@ -161,6 +166,26 @@ function FileExplorer(): JSX.Element {

const [customDragOver, setCustomDragOver] = useState(false)

const trySendFileToIframe = useCallback(
async (path: string): Promise<void> => {
const neuroglancerJSONContent = await readFile('', path)

const { error } = parseNeuroglancerJSON(neuroglancerJSONContent)

if (error) return

const message: SendNeuroglancerJSON = {
type: 'send-neuroglancer-json',
data: {
contents: neuroglancerJSONContent
}
}

broadcast(message)
},
[broadcast]
)

useEffect(() => {
const handleDrop = async (event: DragEvent): Promise<void> => {
event.preventDefault()
Expand All @@ -184,6 +209,8 @@ function FileExplorer(): JSX.Element {
const folderPath = directory(item.path)

setDirectory(folderPath)

trySendFileToIframe(item.path)
}
}

Expand All @@ -209,7 +236,7 @@ function FileExplorer(): JSX.Element {
dropFileRef.current.removeEventListener('dragleave', handleDragLeave)
}
}
}, [directoryPath, dropFileRef])
}, [directoryPath, dropFileRef, trySendFileToIframe])

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ import {
SendDirectoryContents
} from '@renderer/schemas/iframe-message-schema'
import { safeParse } from 'valibot'
import { readFile, writeFile } from './file'
import { useContext, useEffect, useState } from 'react'
import { readFile, writeFile } from '../interfaces/file'
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
import { DirectoryContext } from '@renderer/contexts/DirectoryContext'

export function IFrameManager(): JSX.Element {
export type IFrameContextValue = {
broadcast: (message: IFrameMessage) => void
}

export const IFrameContext = createContext<IFrameContextValue>(null as never)

export function IFrameProvider({ children }: { children: React.ReactNode }): JSX.Element {
// Define allowed origins
const allowedOrigins: string[] = ['http://localhost', 'http://127.0.0.1', 'http://0.0.0.0']

Expand Down Expand Up @@ -44,6 +50,17 @@ export function IFrameManager(): JSX.Element {
// Access the directory context
const data = useContext(DirectoryContext)

const broadcast = useCallback(
(message: IFrameMessage): void => {
iframes.forEach((iframe) => {
iframe.postMessage(message, {
targetOrigin: '*'
})
})
},
[iframes]
)

useEffect(() => {
if (!data) return

Expand All @@ -57,12 +74,8 @@ export function IFrameManager(): JSX.Element {
}

// Send the directory info to the iframes
iframes.forEach((iframe) => {
iframe.postMessage(message, {
targetOrigin: '*'
})
})
}, [data, iframes])
broadcast(message)
}, [data, broadcast])

useEffect(() => {
const listener = (event: MessageEvent): void => {
Expand Down Expand Up @@ -96,7 +109,7 @@ export function IFrameManager(): JSX.Element {
}
}, [])

return <></>
return <IFrameContext.Provider value={{ broadcast }}>{children}</IFrameContext.Provider>
}

async function handleReadFileRequest(
Expand Down
17 changes: 9 additions & 8 deletions src/renderer/src/routes/Root/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Outlet } from 'react-router-dom'

import styles from './Root.module.css'
import TestingPluginProvider from '@renderer/contexts/TestingPluginContext'
import { IFrameManager } from '@renderer/interfaces/iframe'
import { IFrameProvider } from '@renderer/contexts/IFrameContext'

function Root(): JSX.Element {
return (
Expand All @@ -16,13 +16,14 @@ function Root(): JSX.Element {
<AlertProvider>
<ServerProvider>
<DirectoryProvider>
<DragProvider>
<div className={styles.rootArea}>
<MenuPanel />
<Outlet />
<IFrameManager />
</div>
</DragProvider>
<IFrameProvider>
<DragProvider>
<div className={styles.rootArea}>
<MenuPanel />
<Outlet />
</div>
</DragProvider>
</IFrameProvider>
</DirectoryProvider>
</ServerProvider>
</AlertProvider>
Expand Down
69 changes: 28 additions & 41 deletions src/renderer/src/routes/SlicesPage/SlicesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import VisualizeSlicing from './components/VisualizeSlicing/VisualizeSlicing'
import SliceResultSchema from '@renderer/schemas/slice-result-schema'
import { safeParse } from 'valibot'
import SliceStatusResultSchema from '@renderer/schemas/slice-status-result-schema'
import NeuroglancerJSONSchema from '@renderer/schemas/neuroglancer-json-schema'
import { parseNeuroglancerJSON } from '@renderer/schemas/neuroglancer-json-schema'
import SliceVisualizationResultSchema from '@renderer/schemas/slice-visualization-result-schema'
import ConfigurationJSONSchema, {
ConfigurationJSON
Expand Down Expand Up @@ -300,56 +300,43 @@ function useSlicePageState(): SlicePageState {

const neuroglancerJSONContent = await readFile('', entry.value as string)

if (!neuroglancerJSONContent || neuroglancerJSONContent === '') {
addAlert('Invalid Neuroglancer JSON', 'error')
return
}

let json = ''
const { result, error } = parseNeuroglancerJSON(neuroglancerJSONContent)

try {
json = JSON.parse(neuroglancerJSONContent)
} catch (e) {
addAlert('Invalid Neuroglancer JSON', 'error')
if (error) {
addAlert(error, 'error')
return
}

const jsonResult = safeParse(NeuroglancerJSONSchema, json)
const imageLayers: { type: string; name: string }[] = []
const annotationLayers: { type: string; name: string }[] = []

if (jsonResult.success) {
const imageLayers: { type: string; name: string }[] = []
const annotationLayers: { type: string; name: string }[] = []

// Read all image and annotation layers from the Neuroglancer JSON
for (const layer of jsonResult.output['layers']) {
if (layer.type === 'image' && layer.name !== '') {
imageLayers.push(layer)
} else if (layer.type === 'annotation' && layer.name !== '') {
annotationLayers.push(layer)
}
// Read all image and annotation layers from the Neuroglancer JSON
for (const layer of result['layers']) {
if (layer.type === 'image' && layer.name !== '') {
imageLayers.push(layer)
} else if (layer.type === 'annotation' && layer.name !== '') {
annotationLayers.push(layer)
}
}

// Update the options for the image and annotation layer entries
if (entries[0] instanceof CompoundEntry) {
entries[0].getEntries().forEach((entry) => {
if (entry.name === 'neuroglancer_image_layer' && entry instanceof Entry) {
entry.options = imageLayers.map((layer) => layer.name)
// Update the options for the image and annotation layer entries
if (entries[0] instanceof CompoundEntry) {
entries[0].getEntries().forEach((entry) => {
if (entry.name === 'neuroglancer_image_layer' && entry instanceof Entry) {
entry.options = imageLayers.map((layer) => layer.name)

if (imageLayers.length > 0) entry.value = imageLayers[0].name
} else if (
entry.name === 'neuroglancer_annotation_layer' &&
entry instanceof Entry
) {
entry.options = annotationLayers.map((layer) => layer.name)
if (imageLayers.length > 0) entry.value = imageLayers[0].name
} else if (
entry.name === 'neuroglancer_annotation_layer' &&
entry instanceof Entry
) {
entry.options = annotationLayers.map((layer) => layer.name)

if (annotationLayers.length > 0) entry.value = annotationLayers[0].name
}
})
if (annotationLayers.length > 0) entry.value = annotationLayers[0].name
}
})

setEntries([...entries])
}
} else {
addAlert('Invalid Neuroglancer JSON', 'error')
setEntries([...entries])
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/renderer/src/schemas/iframe-message-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,12 @@ export const SaveFileRequestSchema = object({
})

export type SaveFileRequest = InferOutput<typeof SaveFileRequestSchema>

export const SendNeuroglancerJSONSchema = object({
type: pipe(string(), includes('send-neuroglancer-json')),
data: object({
contents: string()
})
})

export type SendNeuroglancerJSON = InferOutput<typeof SendNeuroglancerJSONSchema>
28 changes: 27 additions & 1 deletion src/renderer/src/schemas/neuroglancer-json-schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { array, object, string } from 'valibot'
import { array, InferOutput, object, safeParse, string } from 'valibot'

const NeuroglancerJSONSchema = object({
layers: array(
Expand All @@ -9,4 +9,30 @@ const NeuroglancerJSONSchema = object({
)
})

export type NeuroglancerJSON = InferOutput<typeof NeuroglancerJSONSchema>

export const parseNeuroglancerJSON = (
jsonString: string | null
): { result: NeuroglancerJSON; error: string | null } => {
const errorResult = { result: {} as NeuroglancerJSON, error: 'Invalid Neuroglancer JSON' }

if (!jsonString || jsonString === '') return errorResult

let parsedJSON = ''

try {
parsedJSON = JSON.parse(jsonString)
} catch (e) {
return errorResult
}

const jsonResult = safeParse(NeuroglancerJSONSchema, parsedJSON)

if (jsonResult.success) {
return { result: jsonResult.output, error: null }
} else {
return errorResult
}
}

export default NeuroglancerJSONSchema

0 comments on commit 797cb45

Please sign in to comment.