From 803d089c4db6de8356abbc7d5e2e344fbf9d81cb Mon Sep 17 00:00:00 2001 From: Ali Zaib Date: Wed, 26 Nov 2025 15:31:02 +0100 Subject: [PATCH 01/10] removed rxno from template list --- src/helper/excelParser.ts | 2 -- src/helper/excelTemplate.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/helper/excelParser.ts b/src/helper/excelParser.ts index 1dfe2e0..b417ba6 100644 --- a/src/helper/excelParser.ts +++ b/src/helper/excelParser.ts @@ -44,7 +44,6 @@ type ReactionRow = { status?: string temperature?: string duration?: string - rxno?: string solvent?: string } @@ -117,7 +116,6 @@ export const parseExcelToExportJson = async ( status: rxn.status || null, solvent: rxn.solvent || null, duration: rxn.duration || null, - rxno: rxn.rxno || null, plain_text_description: rxn.description || null, }) diff --git a/src/helper/excelTemplate.ts b/src/helper/excelTemplate.ts index 6fc43d0..9fc0cbb 100644 --- a/src/helper/excelTemplate.ts +++ b/src/helper/excelTemplate.ts @@ -107,7 +107,6 @@ export const generateExcelTemplate = (): Blob => { 'status', 'temperature', 'duration', - 'rxno', ], [ 'reaction1', @@ -116,7 +115,6 @@ export const generateExcelTemplate = (): Blob => { 'Successful', '25', '2h', - 'RXN-001', ], ] const ws_reactions = XLSX.utils.aoa_to_sheet(reactionsData) From 7af5e9cadf7aa1938b7fecf0d27ea1721c5f8b8d Mon Sep 17 00:00:00 2001 From: Ali Zaib Date: Wed, 26 Nov 2025 15:32:42 +0100 Subject: [PATCH 02/10] removed rxno from template list --- src/helper/excelTemplate.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/helper/excelTemplate.ts b/src/helper/excelTemplate.ts index 9fc0cbb..8320f34 100644 --- a/src/helper/excelTemplate.ts +++ b/src/helper/excelTemplate.ts @@ -100,14 +100,7 @@ export const generateExcelTemplate = (): Blob => { // Sheet 2: Reactions const reactionsData = [ - [ - 'identifier', - 'name', - 'description', - 'status', - 'temperature', - 'duration', - ], + ['identifier', 'name', 'description', 'status', 'temperature', 'duration'], [ 'reaction1', 'Esterification', From 552ac8dde8a0dda8e02dd68d84a4e1167ea4ea4e Mon Sep 17 00:00:00 2001 From: Ali Zaib Date: Wed, 26 Nov 2025 15:51:25 +0100 Subject: [PATCH 03/10] added documentaiton link, fixed context menu hiding behind inspector --- .../context-menu/ContextMenuItem.tsx | 2 +- src/app/components/workspace/Header.tsx | 52 ++----------------- 2 files changed, 6 insertions(+), 48 deletions(-) diff --git a/src/app/components/context-menu/ContextMenuItem.tsx b/src/app/components/context-menu/ContextMenuItem.tsx index c6b61c5..251d58e 100644 --- a/src/app/components/context-menu/ContextMenuItem.tsx +++ b/src/app/components/context-menu/ContextMenuItem.tsx @@ -13,7 +13,7 @@ const ContextMenuContainer = ({ }) => (
{ - const [open, setOpen] = useState(false) - - const hide = () => { - setOpen(false) - } - - const handleOpenChange = (newOpen: boolean) => { - setOpen(newOpen) - } return (
@@ -27,42 +15,12 @@ const Header = () => { width={100} src={logo} /> - - { - hide() - }} - > - Documentation - - -
- } - onOpenChange={handleOpenChange} - open={open} - placement="bottomLeft" - title="Actions" - trigger="click" + - - + Documentation + ) } From da5d23c39e5476d12f14128171bf7cec1bd7aa42 Mon Sep 17 00:00:00 2001 From: Ali Zaib Date: Wed, 26 Nov 2025 16:00:52 +0100 Subject: [PATCH 04/10] show kekulized structure of molecule --- src/app/components/tree-view/MoleculeTooltip.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/components/tree-view/MoleculeTooltip.tsx b/src/app/components/tree-view/MoleculeTooltip.tsx index d2bbb44..90732be 100644 --- a/src/app/components/tree-view/MoleculeTooltip.tsx +++ b/src/app/components/tree-view/MoleculeTooltip.tsx @@ -33,7 +33,10 @@ export default function MoleculeTooltip({ const svgBase64 = await ketcher.structService.generateImageAsBase64( input, - { outputFormat: 'svg' }, + { + outputFormat: 'svg', + 'dearomatize-on-load': true, + }, ) // Decode base64 to get actual SVG string const svgString = atob(svgBase64) From 1e3d99a8e6f708ec750f1c598c4c3c6afc19adca Mon Sep 17 00:00:00 2001 From: Ali Zaib Date: Wed, 26 Nov 2025 16:38:41 +0100 Subject: [PATCH 05/10] fix: restore context menu functionality broken by molecule tooltip, samle delete, hide tooltip on context menu open --- src/app/components/tree-view/MoleculeTooltip.tsx | 6 +++++- src/app/components/tree-view/renderItem.tsx | 16 ++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/app/components/tree-view/MoleculeTooltip.tsx b/src/app/components/tree-view/MoleculeTooltip.tsx index 90732be..868a8ad 100644 --- a/src/app/components/tree-view/MoleculeTooltip.tsx +++ b/src/app/components/tree-view/MoleculeTooltip.tsx @@ -7,12 +7,14 @@ interface MoleculeTooltipProps { molfile?: string smiles?: string children: React.ReactNode + 'data-mykey'?: string | number } export default function MoleculeTooltip({ molfile, smiles, children, + 'data-mykey': dataMykey, }: MoleculeTooltipProps) { const [svg, setSvg] = useState(null) const [isHovered, setIsHovered] = useState(false) @@ -72,8 +74,10 @@ export default function MoleculeTooltip({
setIsHovered(false)} + onContextMenu={() => setIsHovered(false)} > {children}
@@ -83,7 +87,7 @@ export default function MoleculeTooltip({ typeof window !== 'undefined' && createPortal(
) =>
diff --git a/src/app/components/zip-download/templates.ts b/src/app/components/zip-download/templates.ts index 2a4a8bf..daf29cd 100644 --- a/src/app/components/zip-download/templates.ts +++ b/src/app/components/zip-download/templates.ts @@ -183,6 +183,9 @@ export const reactionTemplate = { variations: null, plain_text_description: null, plain_text_observation: null, + vessel_size: null, + gaseous: null, + log_data: null, } export const collectionsReactionTemplate = { diff --git a/src/app/components/zip-download/zodSchemes.ts b/src/app/components/zip-download/zodSchemes.ts index 6d01085..bab8873 100644 --- a/src/app/components/zip-download/zodSchemes.ts +++ b/src/app/components/zip-download/zodSchemes.ts @@ -372,7 +372,7 @@ export const reactionSchema = z description: nullableString, timestamp_start: nullableString, timestamp_stop: nullableString, - observation: nullableString, + observation: z.union([nullableString, textObjectSchema]), purification: arraySchema, dangerous_products: arraySchema, tlc_solvents: z.union([nullableString, arraySchema]), @@ -394,9 +394,12 @@ export const reactionSchema = z duration: nullableString, rxno: nullableString, conditions: nullableString, - variations: nullableString, + variations: z.union([nullableString, arraySchema]), plain_text_description: nullableString, plain_text_observation: nullableString, + vessel_size: z.any().optional(), + gaseous: z.any().optional(), + log_data: z.any().optional(), }) .passthrough() export type Reaction = z.infer diff --git a/src/helper/importFromZip.ts b/src/helper/importFromZip.ts index 25476a0..4c18d78 100644 --- a/src/helper/importFromZip.ts +++ b/src/helper/importFromZip.ts @@ -396,6 +396,28 @@ export const importFromJsonOrZip = async (file: File) => { // Build a map to track which containers we've created const createdContainers = new Set() + /** + * Helper to check if a container is a root container (Sample/Reaction/Molecule) + */ + const isRootContainer = (container: ExportContainer): boolean => { + // Check explicit container_type field (ChemScanner format) + if (container.container_type === CONTAINER_TYPE_ROOT) { + return true + } + + // Check by containable_type and parent_id (Chemotion format compatibility) + // A container is a root if it has no parent and contains a Sample/Reaction/Molecule + if (!container.parent_id && container.containable_id) { + return ( + container.containable_type === CONTAINABLE_TYPE_SAMPLE || + container.containable_type === CONTAINABLE_TYPE_REACTION || + container.containable_type === CONTAINABLE_TYPE_MOLECULE + ) + } + + return false + } + /** * Determines the parent UID for a container */ @@ -437,7 +459,7 @@ export const importFromJsonOrZip = async (file: File) => { // Has parent_id - check if parent is a root container const parentContainer = exportData.Container[container.parent_id] - if (parentContainer?.container_type === CONTAINER_TYPE_ROOT) { + if (parentContainer && isRootContainer(parentContainer)) { // Parent is root - the sample/reaction folder should have been created with this UID return containerIdToUidMap[container.parent_id] } @@ -453,11 +475,9 @@ export const importFromJsonOrZip = async (file: File) => { ([, a], [, b]) => { // Prioritize reactions over samples at the same depth const isReactionA = - a.container_type === CONTAINER_TYPE_ROOT && - a.containable_type === CONTAINABLE_TYPE_REACTION + isRootContainer(a) && a.containable_type === CONTAINABLE_TYPE_REACTION const isReactionB = - b.container_type === CONTAINER_TYPE_ROOT && - b.containable_type === CONTAINABLE_TYPE_REACTION + isRootContainer(b) && b.containable_type === CONTAINABLE_TYPE_REACTION if (isReactionA && !isReactionB) return -1 if (!isReactionA && isReactionB) return 1 @@ -568,12 +588,44 @@ export const importFromJsonOrZip = async (file: File) => { } } + // Track skipped container IDs to skip their children too + const skippedContainerIds = new Set() + // Process containers for (const [containerId, container] of sortedContainers) { const containerUid = containerIdToUidMap[containerId] const containableId = container.containable_id const containableType = container.containable_type + // Skip unsupported container types that aren't Samples, Reactions, Molecules, or child containers (analyses/analysis/dataset) + const isUnsupportedRootContainer = + containableType && + containableType !== CONTAINABLE_TYPE_SAMPLE && + containableType !== CONTAINABLE_TYPE_REACTION && + containableType !== CONTAINABLE_TYPE_MOLECULE && + !container.parent_id + + if (isUnsupportedRootContainer) { + console.log( + `Skipping unsupported container type: ${containableType} (${ + container.name || 'unnamed' + })`, + ) + skippedContainerIds.add(containerId) + continue + } + + // Skip containers whose parent was skipped + if (container.parent_id && skippedContainerIds.has(container.parent_id)) { + console.log( + `Skipping child container of skipped parent: ${ + container.name || 'unnamed' + } (${container.container_type})`, + ) + skippedContainerIds.add(containerId) + continue + } + // Determine parent const parentUid = getParentUid(container) diff --git a/src/helper/utils.ts b/src/helper/utils.ts index f7d0aad..2606e3d 100644 --- a/src/helper/utils.ts +++ b/src/helper/utils.ts @@ -169,6 +169,8 @@ const hiddenKeys = Object.freeze([ 'conditions', 'variations', 'observation', + 'vessel_size', + 'gaseous', //Hidden keys for Molecule 'inchikey', 'names', From ba1e586b9137aca003ed790ae1ea44f663bc01a4 Mon Sep 17 00:00:00 2001 From: Ali Zaib Date: Fri, 28 Nov 2025 12:22:46 +0100 Subject: [PATCH 08/10] fixed e2e test for chemotion data --- cypress/e2e/importStructure.cy.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cypress/e2e/importStructure.cy.ts b/cypress/e2e/importStructure.cy.ts index bd637d0..90d9e1c 100644 --- a/cypress/e2e/importStructure.cy.ts +++ b/cypress/e2e/importStructure.cy.ts @@ -22,7 +22,7 @@ describe('Import Structure', () => { // Verify collection and reaction appear in tree cy.get('[data-rct-tree="assignmentTree"]') - .contains('Test Collection') + .contains('3D') .should('be.visible') .parent() .find('button') @@ -30,7 +30,7 @@ describe('Import Structure', () => { .click() cy.get('[data-rct-tree="assignmentTree"]') - .contains('Test Reaction') + .contains('CHI-R41') .should('be.visible') }) @@ -46,14 +46,14 @@ describe('Import Structure', () => { // Expand to reaction cy.get('[data-rct-tree="assignmentTree"]') - .contains('Test Collection') + .contains('3D') .parent() .find('button') .first() .click() cy.get('[data-rct-tree="assignmentTree"]') - .contains('Test Reaction') + .contains('CHI-R41') .parent() .find('button') .first() @@ -61,7 +61,7 @@ describe('Import Structure', () => { // Verify samples are nested under reaction cy.get('[data-rct-tree="assignmentTree"]').within(() => { - cy.contains('Product Sample').should('exist') + cy.contains('reactant').should('exist') }) }) @@ -110,7 +110,7 @@ describe('Import Structure', () => { // Verify data appears in the tree (which confirms it was saved to IndexedDB) cy.get('[data-rct-tree="assignmentTree"]') - .contains('Test Collection') + .contains('3D') .should('be.visible') .parent() .find('button') @@ -118,7 +118,7 @@ describe('Import Structure', () => { .click() cy.get('[data-rct-tree="assignmentTree"]') - .contains('Test Reaction') + .contains('CHI-R41') .should('be.visible') }) }) From 5abde2e2785ede009367b893428f1df8a4b65d7e Mon Sep 17 00:00:00 2001 From: Ali Zaib Date: Fri, 28 Nov 2025 13:00:17 +0100 Subject: [PATCH 09/10] hide collection keys --- src/helper/utils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/helper/utils.ts b/src/helper/utils.ts index 2606e3d..665c538 100644 --- a/src/helper/utils.ts +++ b/src/helper/utils.ts @@ -187,6 +187,10 @@ const hiddenKeys = Object.freeze([ 'molecule_svg_file', 'mol3k', 'mol2k', + 'inventory_id', + 'devicedescription_detail_level', + 'celllinesample_detail_level', + 'tabs_segment' ]) // Keys to hide for specific schemas From cea9bef0abc22277463b4f853f986a13c9862532 Mon Sep 17 00:00:00 2001 From: Ali Zaib Date: Fri, 28 Nov 2025 13:08:56 +0100 Subject: [PATCH 10/10] hide collection keys --- src/helper/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helper/utils.ts b/src/helper/utils.ts index 665c538..c5bd825 100644 --- a/src/helper/utils.ts +++ b/src/helper/utils.ts @@ -190,7 +190,7 @@ const hiddenKeys = Object.freeze([ 'inventory_id', 'devicedescription_detail_level', 'celllinesample_detail_level', - 'tabs_segment' + 'tabs_segment', ]) // Keys to hide for specific schemas