Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#4853-Search monomer in library by IDT #5373

Merged
merged 13 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const MonomerList = ({
const presets = useAppSelector(selectFilteredPresets);
const activeTool = useAppSelector(selectEditorActiveTool);
const isFavoriteTab = libraryName === MONOMER_LIBRARY_FAVORITES;

const items = !isFavoriteTab
? selectMonomersInCategory(monomers, libraryName)
: ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ interface EditorState {
activeTool: string;
editor: CoreEditor | undefined;
preview: EditorStatePreview;
position: PresetPosition | undefined;
}

const initialState: EditorState = {
Expand All @@ -92,6 +93,7 @@ const initialState: EditorState = {
monomer: undefined,
style: {},
},
position: undefined,
};

export const editorSlice: Slice = createSlice({
Expand All @@ -110,6 +112,9 @@ export const editorSlice: Slice = createSlice({
selectTool: (state, action: PayloadAction<string>) => {
state.activeTool = action.payload;
},
setPosition: (state, action: PayloadAction<PresetPosition>) => {
state.position = action.payload;
},

createEditor: (
state,
Expand Down Expand Up @@ -144,13 +149,17 @@ export const {
createEditor,
showPreview,
destroyEditor,
setPosition,
} = editorSlice.actions;

export const selectEditorIsReady = (state: RootState) => state.editor.isReady;
export const selectShowPreview = (state: RootState): EditorStatePreview =>
state.editor.preview;
export const selectEditorActiveTool = (state: RootState) =>
state.editor.activeTool;
export const selectEditorPosition = (
state: RootState,
): PresetPosition | undefined => state.editor.position;
// TODO: Specify the types.
// export const selectEditorIsReady = (state: RootState): EditorState['isReady'] =>
// state.editor.isReady;
Expand Down
120 changes: 116 additions & 4 deletions packages/ketcher-macromolecules/src/state/library/librarySlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,19 +308,131 @@ export const selectAmbiguousMonomersInFavorites = (
};

export const selectFilteredMonomers = createSelector(
(state) => state.library,
(state: RootState) => state.library,
(state): Array<MonomerItemType & { favorite: boolean }> => {
const { searchFilter, monomers, favorites } = state;
const normalizedSearchFilter = searchFilter.toLowerCase();

return monomers
.filter((item: MonomerItemType) => {
const { Name = '', MonomerName = '' } = item.props;
const { Name = '', MonomerName = '', idtAliases } = item.props;
const monomerName = Name.toLowerCase();
const monomerNameFull = MonomerName.toLowerCase();
const normalizedSearchFilter = searchFilter.toLowerCase();

const idtBase = idtAliases?.base?.toLowerCase();

const idtModifications = idtAliases?.modifications
? Object.values(idtAliases.modifications)
.map((mod) => mod.toLowerCase())
.join(' ')
: '';

if (normalizedSearchFilter === '/') {
return Boolean(idtBase || idtModifications);
}

if (normalizedSearchFilter.includes('/')) {
const parts = normalizedSearchFilter.split('/');

if (parts.length > 3 || (parts.length === 3 && parts[2] !== '')) {
return false;
}

if (parts.length === 3 && parts[1] !== '') {
const textBetweenSlashes = parts[1];

const matchesIdtBase =
idtBase &&
idtBase.length === textBetweenSlashes.length &&
Array.from(idtBase).every(
(char, index) => char === textBetweenSlashes[index],
);

const matchesIdtModifications = idtModifications
? idtModifications
.split(' ')
.some(
(mod) =>
mod.length === textBetweenSlashes.length &&
Array.from(mod).every(
(char, index) => char === textBetweenSlashes[index],
),
)
: false;

return matchesIdtBase || matchesIdtModifications;
}

const searchBeforeSlash = parts[0];
const searchAfterSlash = parts[1];

if (
normalizedSearchFilter.startsWith('/') &&
normalizedSearchFilter.length > 1
) {
const aliasRest = normalizedSearchFilter.slice(1);
return (
(idtBase && idtBase.startsWith(aliasRest)) ||
(idtModifications &&
idtModifications
.split(' ')
.some((mod) => mod.startsWith(aliasRest)))
);
}

if (
normalizedSearchFilter.endsWith('/') &&
normalizedSearchFilter.length > 1
) {
const aliasRest = normalizedSearchFilter.slice(0, -1);
const aliasLastSymbol =
normalizedSearchFilter[normalizedSearchFilter.length - 2];

return (
(idtBase &&
idtBase.endsWith(aliasRest) &&
idtBase[idtBase.length - 1] === aliasLastSymbol) ||
(idtModifications &&
idtModifications
.split(' ')
.some(
(mod) =>
mod.endsWith(aliasRest) &&
mod[mod.length - 1] === aliasLastSymbol,
))
);
}

const matchesIdtBase =
idtBase &&
idtBase.startsWith(searchAfterSlash) &&
idtBase.endsWith(searchBeforeSlash);
const matchesIdtModifications = idtModifications
? idtModifications
.split(' ')
.some(
(mod) =>
mod.startsWith(searchAfterSlash) &&
mod.endsWith(searchBeforeSlash),
)
: false;

return matchesIdtBase || matchesIdtModifications;
}

const matchesIdtBase = idtBase
? idtBase.includes(normalizedSearchFilter)
: false;
const matchesIdtModifications = idtModifications
? idtModifications.includes(normalizedSearchFilter)
: false;

const cond =
monomerName.includes(normalizedSearchFilter) ||
monomerNameFull.includes(normalizedSearchFilter);
monomerNameFull.includes(normalizedSearchFilter) ||
matchesIdtBase ||
matchesIdtModifications;

return cond;
})
.map((item: MonomerItemType) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
toggleCachedCustomRnaPresetFavorites,
} from 'helpers/manipulateCachedRnaPresets';
import { transformRnaPresetToRnaLabeledPreset } from './rnaBuilderSlice.helper';
import { PresetPosition, selectEditorPosition } from 'state/common';

export enum RnaBuilderPresetsItem {
Presets = 'Presets',
Expand Down Expand Up @@ -404,22 +405,95 @@ export const selectAllPresets = (
const { presetsDefault = [], presetsCustom = [] } = state.rnaBuilder;
return [...presetsDefault, ...presetsCustom];
};

export const selectFilteredPresets = (
state,
): Array<IRnaPreset & { favorite: boolean }> => {
const { searchFilter } = state.library;
const presetsAll = selectAllPresets(state);
const position = selectEditorPosition(state) ?? PresetPosition.Library;
const searchText = searchFilter.toLowerCase();

return presetsAll.filter((item: IRnaPreset) => {
const name = item.name?.toLowerCase();
const sugarName = item.sugar?.label?.toLowerCase();
const phosphateName = item.phosphate?.label?.toLowerCase();
const baseName = item.base?.label?.toLowerCase();
const searchText = searchFilter.toLowerCase();
const idtName = item.idtAliases?.base?.toLowerCase();
const modifications = item.idtAliases?.modifications;
let transformedIdtText = idtName;

if (item.name?.includes('MOE') && idtName) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not rely on monomer name because we will have other monomers with idt aliases in future

const base = idtName;
const endpoint5 = modifications?.endpoint5 ?? `5${base}`;
const internal = modifications?.internal ?? `i${base}`;
const endpoint3 = modifications?.endpoint3 ?? `3${base}`;

switch (position) {
case PresetPosition.Library:
transformedIdtText = `${endpoint5}, ${internal}`;
break;
case PresetPosition.ChainStart:
transformedIdtText = endpoint5;
break;
case PresetPosition.ChainMiddle:
transformedIdtText = internal;
break;
case PresetPosition.ChainEnd:
transformedIdtText = endpoint3;
break;
}
}
Comment on lines +432 to +446
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Position does not play any role in library.


const slashCount = (searchText.match(/\//g) || []).length;
const parts = searchText.split('/');

if (slashCount >= 2 && parts[2] !== undefined && parts[2] !== '') {
return false;
}

if (searchText.startsWith('/') && searchText.length > 1) {
const aliasRest = searchText.slice(1);
return (
transformedIdtText?.toLowerCase().startsWith(aliasRest) ||
idtName?.startsWith(aliasRest) ||
(modifications &&
Object.values(modifications).some((mod) =>
mod?.toLowerCase().startsWith(aliasRest),
))
);
}

if (searchText.endsWith('/') && searchText.length > 1) {
const aliasRest = searchText.slice(0, -1);
const aliasLastSymbol = searchText[searchText.length - 2];

return (
(transformedIdtText?.toLowerCase().endsWith(aliasRest) &&
transformedIdtText[transformedIdtText.length - 1] ===
aliasLastSymbol) ||
(idtName?.endsWith(aliasRest) &&
idtName[idtName.length - 1] === aliasLastSymbol) ||
(modifications &&
Object.values(modifications).some(
(mod) =>
mod?.toLowerCase().endsWith(aliasRest) &&
mod[mod.length - 1] === aliasLastSymbol,
))
);
}

if (searchText === '/') {
return !!item.idtAliases;
}

const cond =
name?.includes(searchText) ||
sugarName?.includes(searchText) ||
phosphateName?.includes(searchText) ||
baseName?.includes(searchText);
baseName?.includes(searchText) ||
transformedIdtText?.toLowerCase().includes(searchText);

return cond;
});
};
Expand Down
Loading