From 6b5f52958665fa31c3837f176ebc7652d5079673 Mon Sep 17 00:00:00 2001 From: Derick Sayavong Date: Sun, 26 Jan 2025 18:56:17 -0700 Subject: [PATCH 1/8] Plasmid files are created and put into a Plasmid directory --- .../activities/explorer/ExplorerActivityView.jsx | 2 +- src/components/activities/explorer/ExplorerList.jsx | 12 ++++++++---- src/objectTypes.js | 13 +++++++++++++ src/panels.js | 4 ++-- src/redux/hooks/workingDirectoryHooks.js | 11 +++++++---- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/components/activities/explorer/ExplorerActivityView.jsx b/src/components/activities/explorer/ExplorerActivityView.jsx index e04c4d7..25285d1 100644 --- a/src/components/activities/explorer/ExplorerActivityView.jsx +++ b/src/components/activities/explorer/ExplorerActivityView.jsx @@ -24,7 +24,7 @@ export default function ExplorerActivityView({ }) { return workingDirectory ? <> - +
Switch Folder diff --git a/src/components/activities/explorer/ExplorerList.jsx b/src/components/activities/explorer/ExplorerList.jsx index 2e41121..acdfa57 100644 --- a/src/components/activities/explorer/ExplorerList.jsx +++ b/src/components/activities/explorer/ExplorerList.jsx @@ -6,15 +6,19 @@ import ExplorerListItem from './ExplorerListItem' import SaveIndicatorDisplay from '../../saveIndicatorDisplay' -export default function ExplorerList({currentDirectory}) { +export default function ExplorerList({workDir}) { // grab file handles const files = useFiles() // handle creation const createFile = useCreateFile() - const handleCreateObject = objectType => fileName => { - createFile(fileName + objectType.extension, objectType.id) + const handleCreateObject = objectType => async fileName => { + if(objectType.title === "Plasmid"){ // Retrieve Plasmid directory, if it doesn't exist create it first + tempDirectory = await workDir.getDirectoryHandle("Plasmid", { create: true }); + + } + createFile(fileName + objectType.extension, objectType.id, tempDirectory) } // generate DragObjects based on data @@ -29,7 +33,7 @@ export default function ExplorerList({currentDirectory}) { return ( - Current Folder: {currentDirectory} + Current Folder: {workDir.name} ({ diff --git a/src/redux/hooks/workingDirectoryHooks.js b/src/redux/hooks/workingDirectoryHooks.js index 4460a9d..f56c92d 100644 --- a/src/redux/hooks/workingDirectoryHooks.js +++ b/src/redux/hooks/workingDirectoryHooks.js @@ -46,12 +46,13 @@ export function useWorkingDirectory() { // Action hooks + export function useCreateFile() { const dispatch = useDispatch() const openPanel = useOpenPanel() const workDir = useSelector(state => state.workingDirectory.directoryHandle) - return (fileName, objectType) => { - workDir.getFileHandle(fileName, { create: true }) + return (fileName, objectType, directory = workDir) => { // Optional arg directory in which the file will be created + directory.getFileHandle(fileName, { create: true }) .then(fileHandle => { addFileMetadata(fileHandle, { objectType }) dispatch(actions.addFile(fileHandle)) @@ -103,20 +104,22 @@ export function useSafeName(baseName) { // Utility -async function findFilesInDirectory(dirHandle) { +export async function findFilesInDirectory(dirHandle) { const files = [] + // loop through async iterator of file names (called keys here) for await (const handle of dirHandle.values()) { if (handle.kind == 'file') { await addFileMetadata(handle) files.push(handle) } + } return files } - + //TODO: Assings object types when opening a folder, check extension or contents, might be better to just go into sub dirs themselves and use that as a way to list async function addFileMetadata(handle, { objectType } = {}) { // handle.id = uuidv4() handle.id = handle.name From ae0fce556c5332f16f50bb27d481a59983d8cf02 Mon Sep 17 00:00:00 2001 From: Derick Sayavong Date: Wed, 29 Jan 2025 17:38:54 -0700 Subject: [PATCH 2/8] Files are correctly listed for their respective subdirectories --- .../activities/explorer/ExplorerList.jsx | 3 +- src/objectTypes.js | 60 +++++++++++++++---- src/redux/hooks/workingDirectoryHooks.js | 30 +++++++--- 3 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/components/activities/explorer/ExplorerList.jsx b/src/components/activities/explorer/ExplorerList.jsx index acdfa57..acfa46b 100644 --- a/src/components/activities/explorer/ExplorerList.jsx +++ b/src/components/activities/explorer/ExplorerList.jsx @@ -10,12 +10,13 @@ export default function ExplorerList({workDir}) { // grab file handles const files = useFiles() + let tempDirectory; // handle creation const createFile = useCreateFile() const handleCreateObject = objectType => async fileName => { if(objectType.title === "Plasmid"){ // Retrieve Plasmid directory, if it doesn't exist create it first - tempDirectory = await workDir.getDirectoryHandle("Plasmid", { create: true }); + tempDirectory = await workDir.getDirectoryHandle("plasmid", { create: true }); } createFile(fileName + objectType.extension, objectType.id, tempDirectory) diff --git a/src/objectTypes.js b/src/objectTypes.js index 973adad..c2de515 100644 --- a/src/objectTypes.js +++ b/src/objectTypes.js @@ -1,7 +1,9 @@ import { BiWorld } from "react-icons/bi" import { IoAnalyticsSharp } from "react-icons/io5" import { TbComponents } from "react-icons/tb" - +import { GrTestDesktop } from "react-icons/gr"; +import { MdAlignVerticalTop } from "react-icons/md"; +import { VscOutput } from "react-icons/vsc"; export const ObjectTypes = { SBOL: { id: "synbio.object-type.sbol", @@ -43,6 +45,34 @@ export const ObjectTypes = { extension: '.analysis', directory: "main" }, + Experiment: { + id: "synbio.object-type.experiment", + title: "Experiments", + listTitle: "Experiments", + fileNameMatch: /\.xdc/, + icon: GrTestDesktop, + createable: true, + uploadable: false, + extension: ".xdc", + }, + Metadata: { + id: "synbio.object-type.experimental-data", + title: "Experimental Metadata", + listTitle: "Experimental Metadata", + fileNameMatch: /\.(xlsm|xlsx)$/, + icon: MdAlignVerticalTop, + createable: false, + uploadable: true, + }, + Output: { + id: "synbio.object-type.output-data", + title: "Plate Reader Outputs", + listTitle: "Plate Reader Outputs", + fileNameMatch: /\.(xlsm|xlsx)$/, + icon: VscOutput, + createable: false, + uploadable: true, + }, Plasmids:{ id: "synbio.object-type.plasmid", title: "Plasmid", @@ -50,7 +80,8 @@ export const ObjectTypes = { createable: true, extension: '.xml', icon: TbComponents, - directory: 'Plasmid' + directory: 'Plasmid', + fileNameMatch: /\.xml$/ } } @@ -58,17 +89,26 @@ export function getObjectType(id) { return Object.values(ObjectTypes).find(ot => ot.id == id) } -export async function classifyFile(file) { +export async function classifyFile(file, subDirectoryName) { // try to match by file name const matchFromFileName = Object.values(ObjectTypes).find( ot => ot.fileNameMatch?.test(file.name) )?.id - if(matchFromFileName) - return matchFromFileName - + if (!subDirectoryName && matchFromFileName && matchFromFileName != ObjectTypes.Metadata.id && matchFromFileName != ObjectTypes.Output.id && matchFromFileName != ObjectTypes.Plasmids.id) { + return matchFromFileName; + } else if + (subDirectoryName != null && subDirectoryName.toLowerCase() === "output" && ObjectTypes.Output.fileNameMatch?.test(file.name)) { + return ObjectTypes.Output.id; + } else if (subDirectoryName != null && subDirectoryName.toLowerCase() === "metadata" && ObjectTypes.Metadata.fileNameMatch?.test(file.name)) { + return ObjectTypes.Metadata.id; + } else if (subDirectoryName != null && subDirectoryName.toLowerCase() === "plasmid" && ObjectTypes.Plasmids.fileNameMatch?.test(file.name)) { + return ObjectTypes.Plasmids.id; + } // otherwise, read file content - const fileContent = await (await file.getFile()).text() - return Object.values(ObjectTypes).find( - ot => ot.fileMatch?.test(fileContent) - )?.id + if(subDirectoryName == null){ + const fileContent = await (await file.getFile()).text() + return Object.values(ObjectTypes).find( + ot => ot.fileMatch?.test(fileContent) + )?.id + } } \ No newline at end of file diff --git a/src/redux/hooks/workingDirectoryHooks.js b/src/redux/hooks/workingDirectoryHooks.js index f56c92d..5a48eab 100644 --- a/src/redux/hooks/workingDirectoryHooks.js +++ b/src/redux/hooks/workingDirectoryHooks.js @@ -54,7 +54,7 @@ export function useCreateFile() { return (fileName, objectType, directory = workDir) => { // Optional arg directory in which the file will be created directory.getFileHandle(fileName, { create: true }) .then(fileHandle => { - addFileMetadata(fileHandle, { objectType }) + addFileMetadata(fileHandle, null, { objectType }) dispatch(actions.addFile(fileHandle)) openPanel(fileHandle) }) @@ -73,7 +73,7 @@ export function useCreateFileWithFilePicker() { }] }) .then(fileHandle => { - addFileMetadata(fileHandle) + addFileMetadata(fileHandle, null) dispatch(actions.addFile(fileHandle)) openPanel(fileHandle) }) @@ -111,19 +111,35 @@ export async function findFilesInDirectory(dirHandle) { // loop through async iterator of file names (called keys here) for await (const handle of dirHandle.values()) { if (handle.kind == 'file') { - await addFileMetadata(handle) + await addFileMetadata(handle, null) files.push(handle) } } + // Check for subfolders "output" and "metadata" + for await (const [name, subHandle] of dirHandle.entries()) { + if (subHandle.kind === 'directory' && (name.toLowerCase() === 'output' || name.toLowerCase() === 'metadata' || name.toLowerCase() === "plasmid")) { + for await (const handle of subHandle.values()) { + if (handle.kind === 'file') { + await addFileMetadata(handle, name) + files.push(handle) + } + } + } + } + return files } - //TODO: Assings object types when opening a folder, check extension or contents, might be better to just go into sub dirs themselves and use that as a way to list -async function addFileMetadata(handle, { objectType } = {}) { + +async function addFileMetadata(handle, subDirectoryName, { objectType } = {}) { // handle.id = uuidv4() - handle.id = handle.name - handle.objectType = objectType || await classifyFile(handle) + if (subDirectoryName === null) { + handle.id = handle.name; + } else { + handle.id = (subDirectoryName + '/' + handle.name); + } + handle.objectType = objectType || await classifyFile(handle, subDirectoryName) } export function titleFromFileName(fileName) { From feae9156b4fd017d0c54c93a03793f9c9d36b079 Mon Sep 17 00:00:00 2001 From: Derick Sayavong Date: Tue, 4 Feb 2025 22:40:32 -0700 Subject: [PATCH 3/8] Sending data to SBOLCanvas app that a plasmid type is opened --- src/components/panels/Panel.jsx | 4 ++-- src/components/panels/sbol-editor/CanvasFrame.jsx | 6 +++--- src/components/panels/sbol-editor/SBOLEditorPanel.jsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/panels/Panel.jsx b/src/components/panels/Panel.jsx index a1e122a..183245b 100644 --- a/src/components/panels/Panel.jsx +++ b/src/components/panels/Panel.jsx @@ -23,12 +23,12 @@ const Tab = forwardRef(({ id, ...props }, ref) => { }) function Content({ id, ...props }) { - const panelType = usePanelType(id) + const fileHandle = usePanelProperty(id, 'fileHandle') return ( - + ) } diff --git a/src/components/panels/sbol-editor/CanvasFrame.jsx b/src/components/panels/sbol-editor/CanvasFrame.jsx index 95ce8e2..3702ac2 100644 --- a/src/components/panels/sbol-editor/CanvasFrame.jsx +++ b/src/components/panels/sbol-editor/CanvasFrame.jsx @@ -5,10 +5,9 @@ import { PanelContext } from './SBOLEditorPanel' import { usePanelProperty } from "../../../redux/hooks/panelsHooks" -export default function CanvasFrame() { +export default function CanvasFrame({fileTypeObjectId}) { const panelId = useContext(PanelContext) - // state containing full SBOL content const [sbolContent, setSBOLContent] = usePanelProperty(panelId, "sbol", false) @@ -50,8 +49,9 @@ export default function CanvasFrame() { // post message iframeRef.current.contentWindow.postMessage( + sbolContent ? - { sbol: sbolContent } : // either send SBOL content + { sbol: sbolContent, panelType: fileTypeObjectId} : // either send SBOL content 'hello canvas', // or send dummy message import.meta.env.VITE_SBOL_CANVAS_URL ) diff --git a/src/components/panels/sbol-editor/SBOLEditorPanel.jsx b/src/components/panels/sbol-editor/SBOLEditorPanel.jsx index 0b01779..ddd5734 100644 --- a/src/components/panels/sbol-editor/SBOLEditorPanel.jsx +++ b/src/components/panels/sbol-editor/SBOLEditorPanel.jsx @@ -6,7 +6,7 @@ import { useSelector } from 'react-redux' export const PanelContext = createContext() -export default function SBOLEditorPanel({id}) { +export default function SBOLEditorPanel({id, fileObjectTypeId}) { const activePanel = useSelector(state => state.panels.active) return ( @@ -27,7 +27,7 @@ export default function SBOLEditorPanel({id}) {
*/} - + ) From 81f3f9fea43a8b70a5bfb7f3ed8a6a68fe67c118 Mon Sep 17 00:00:00 2001 From: Derick Sayavong Date: Wed, 5 Feb 2025 17:38:44 -0700 Subject: [PATCH 4/8] Send panel type data even if there is no SBOL data --- src/components/panels/sbol-editor/CanvasFrame.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/panels/sbol-editor/CanvasFrame.jsx b/src/components/panels/sbol-editor/CanvasFrame.jsx index 3702ac2..bfe7b0c 100644 --- a/src/components/panels/sbol-editor/CanvasFrame.jsx +++ b/src/components/panels/sbol-editor/CanvasFrame.jsx @@ -52,7 +52,7 @@ export default function CanvasFrame({fileTypeObjectId}) { sbolContent ? { sbol: sbolContent, panelType: fileTypeObjectId} : // either send SBOL content - 'hello canvas', // or send dummy message + {panelType: fileTypeObjectId}, // if no sbolContent, just send in panel type import.meta.env.VITE_SBOL_CANVAS_URL ) } From 5fe65f6932621c88996d4967727c3bf64b7b1eba Mon Sep 17 00:00:00 2001 From: Derick Sayavong Date: Fri, 7 Feb 2025 20:06:24 -0700 Subject: [PATCH 5/8] Able to delete plasmid files --- src/commands.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/commands.js b/src/commands.js index 84dc02a..ed3f4d0 100644 --- a/src/commands.js +++ b/src/commands.js @@ -19,13 +19,18 @@ export default { execute: async fileNameOrId => { // try to find file by ID first, then by name const file = findFileByNameOrId(fileNameOrId) - // quit if this file doesn't exist if (!file) return "File doesn't exist." - - // delete file from disk - await store.getState().workingDirectory.directoryHandle?.removeEntry(file.name) + + // delete file from disk, + if(file.id.split("/")[0] === "plasmid"){ + const tempDirectory = await store.getState().workingDirectory.directoryHandle.getDirectoryHandle("plasmid") + await tempDirectory.removeEntry(file.name) + } + else{ + await store.getState().workingDirectory.directoryHandle?.removeEntry(file.name) + } // close panel if it's open store.dispatch(panelsActions.closePanel(file.id)) From 59c26787ed8cc889ee97b8594d2c347a9fb2a526 Mon Sep 17 00:00:00 2001 From: Derick Sayavong Date: Mon, 10 Feb 2025 11:18:20 -0700 Subject: [PATCH 6/8] Deletion in subdirectories --- src/commands.js | 9 +++++---- src/redux/hooks/workingDirectoryHooks.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/commands.js b/src/commands.js index ed3f4d0..6b00e7b 100644 --- a/src/commands.js +++ b/src/commands.js @@ -23,12 +23,13 @@ export default { if (!file) return "File doesn't exist." - // delete file from disk, - if(file.id.split("/")[0] === "plasmid"){ - const tempDirectory = await store.getState().workingDirectory.directoryHandle.getDirectoryHandle("plasmid") + // delete file from disk, try to see if in subdirectory first else delete from root + const directory = file.id.split("/")[0] + try{ + const tempDirectory = await store.getState().workingDirectory.directoryHandle.getDirectoryHandle(directory) await tempDirectory.removeEntry(file.name) } - else{ + catch{ await store.getState().workingDirectory.directoryHandle?.removeEntry(file.name) } diff --git a/src/redux/hooks/workingDirectoryHooks.js b/src/redux/hooks/workingDirectoryHooks.js index 5a48eab..fb50d8c 100644 --- a/src/redux/hooks/workingDirectoryHooks.js +++ b/src/redux/hooks/workingDirectoryHooks.js @@ -54,7 +54,7 @@ export function useCreateFile() { return (fileName, objectType, directory = workDir) => { // Optional arg directory in which the file will be created directory.getFileHandle(fileName, { create: true }) .then(fileHandle => { - addFileMetadata(fileHandle, null, { objectType }) + addFileMetadata(fileHandle, directory.name, { objectType }) dispatch(actions.addFile(fileHandle)) openPanel(fileHandle) }) From cf2b7d00f004f2978d124d5dae918c5eda1c51d5 Mon Sep 17 00:00:00 2001 From: Derick Sayavong Date: Wed, 12 Feb 2025 15:13:15 -0700 Subject: [PATCH 7/8] Removed Kerem's code as they are not fully implemented yet --- src/objectTypes.js | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/src/objectTypes.js b/src/objectTypes.js index c2de515..604a95b 100644 --- a/src/objectTypes.js +++ b/src/objectTypes.js @@ -45,34 +45,6 @@ export const ObjectTypes = { extension: '.analysis', directory: "main" }, - Experiment: { - id: "synbio.object-type.experiment", - title: "Experiments", - listTitle: "Experiments", - fileNameMatch: /\.xdc/, - icon: GrTestDesktop, - createable: true, - uploadable: false, - extension: ".xdc", - }, - Metadata: { - id: "synbio.object-type.experimental-data", - title: "Experimental Metadata", - listTitle: "Experimental Metadata", - fileNameMatch: /\.(xlsm|xlsx)$/, - icon: MdAlignVerticalTop, - createable: false, - uploadable: true, - }, - Output: { - id: "synbio.object-type.output-data", - title: "Plate Reader Outputs", - listTitle: "Plate Reader Outputs", - fileNameMatch: /\.(xlsm|xlsx)$/, - icon: VscOutput, - createable: false, - uploadable: true, - }, Plasmids:{ id: "synbio.object-type.plasmid", title: "Plasmid", @@ -96,12 +68,8 @@ export async function classifyFile(file, subDirectoryName) { )?.id if (!subDirectoryName && matchFromFileName && matchFromFileName != ObjectTypes.Metadata.id && matchFromFileName != ObjectTypes.Output.id && matchFromFileName != ObjectTypes.Plasmids.id) { return matchFromFileName; - } else if - (subDirectoryName != null && subDirectoryName.toLowerCase() === "output" && ObjectTypes.Output.fileNameMatch?.test(file.name)) { - return ObjectTypes.Output.id; - } else if (subDirectoryName != null && subDirectoryName.toLowerCase() === "metadata" && ObjectTypes.Metadata.fileNameMatch?.test(file.name)) { - return ObjectTypes.Metadata.id; - } else if (subDirectoryName != null && subDirectoryName.toLowerCase() === "plasmid" && ObjectTypes.Plasmids.fileNameMatch?.test(file.name)) { + } + else if (subDirectoryName != null && subDirectoryName.toLowerCase() === "plasmid" && ObjectTypes.Plasmids.fileNameMatch?.test(file.name)) { return ObjectTypes.Plasmids.id; } // otherwise, read file content From 02f6f44c6c807f712b0f3b3445361207ea8b8c42 Mon Sep 17 00:00:00 2001 From: Derick Sayavong Date: Wed, 12 Feb 2025 15:23:50 -0700 Subject: [PATCH 8/8] Removed Metadata and Output objectTypes --- src/objectTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/objectTypes.js b/src/objectTypes.js index 604a95b..05cc12f 100644 --- a/src/objectTypes.js +++ b/src/objectTypes.js @@ -66,7 +66,7 @@ export async function classifyFile(file, subDirectoryName) { const matchFromFileName = Object.values(ObjectTypes).find( ot => ot.fileNameMatch?.test(file.name) )?.id - if (!subDirectoryName && matchFromFileName && matchFromFileName != ObjectTypes.Metadata.id && matchFromFileName != ObjectTypes.Output.id && matchFromFileName != ObjectTypes.Plasmids.id) { + if (!subDirectoryName && matchFromFileName && matchFromFileName && matchFromFileName != ObjectTypes.Plasmids.id) { return matchFromFileName; } else if (subDirectoryName != null && subDirectoryName.toLowerCase() === "plasmid" && ObjectTypes.Plasmids.fileNameMatch?.test(file.name)) {