diff --git a/frontend/AllAtomPredictMixin.vue b/frontend/AllAtomPredictMixin.vue new file mode 100644 index 0000000..b58dfb7 --- /dev/null +++ b/frontend/AllAtomPredictMixin.vue @@ -0,0 +1,61 @@ + \ No newline at end of file diff --git a/frontend/FoldMasonSearch.vue b/frontend/FoldMasonSearch.vue index 77ca401..481fe9d 100644 --- a/frontend/FoldMasonSearch.vue +++ b/frontend/FoldMasonSearch.vue @@ -22,6 +22,17 @@ diff --git a/frontend/MDI.js b/frontend/MDI.js index 95cc866..ab93330 100644 --- a/frontend/MDI.js +++ b/frontend/MDI.js @@ -1,96 +1,108 @@ import { - mdiAlertCircleOutline as AlertCircleOutline, - mdiArrowRightCircle as ArrowRightCircle, - mdiArrowRightCircleOutline as ArrowRightCircleOutline, - mdiAxisZRotateCounterclockwise as AxisZRotateCounterclockwise, - mdiChevronLeft as ChevronLeft, - mdiChevronRight as ChevronRight, - mdiCircle as Circle, - mdiCircleHalf as CircleHalf, - mdiClockOutline as ClockOutline, - mdiCloudDownloadOutline as CloudDownloadOutline, - mdiDelete as Delete, - mdiDns as Dns, - mdiFile as File, - mdiFileUpload as FileUpload, - mdiFileDownloadOutline as FileDownloadOutline, - mdiFormatListBulleted as FormatListBulleted, - mdiFullscreen as Fullscreen, - mdiHelpCircleOutline as HelpCircleOutline, - mdiHistory as History, - mdiLabel as Label, - mdiLabelOutline as LabelOutline, - mdiMagnify as Magnify, - mdiMinusBox as MinusBox, - mdiNotificationClearAll as NotificationClearAll, - mdiPlusBox as PlusBox, - mdiProgressWrench as ProgressWrench, - mdiReorderHorizontal as ReorderHorizontal, - mdiRestore as Restore, - mdiTableLarge as TableLarge, - mdiTune as Tune, - mdiLayersSearchOutline as LayersSearchOutline, - mdiCloseCircle as CloseCircle, - mdiCloseCircleOutline as CloseCircleOutline, - mdiWall as Wall, - mdiTextBoxOutline as TextBoxOutline, - mdiPencil as Pencil, -} from '@mdi/js'; + mdiAlertCircleOutline as AlertCircleOutline, + mdiArrowRightCircle as ArrowRightCircle, + mdiArrowRightCircleOutline as ArrowRightCircleOutline, + mdiAxisZRotateCounterclockwise as AxisZRotateCounterclockwise, + mdiChevronLeft as ChevronLeft, + mdiChevronRight as ChevronRight, + mdiChevronUp as ChevronUp, + mdiChevronDown as ChevronDown, + mdiCircle as Circle, + mdiCircleHalf as CircleHalf, + mdiClockOutline as ClockOutline, + mdiCloudDownloadOutline as CloudDownloadOutline, + mdiDelete as Delete, + mdiDns as Dns, + mdiFile as File, + mdiFileUpload as FileUpload, + mdiFileDownloadOutline as FileDownloadOutline, + mdiFormatListBulleted as FormatListBulleted, + mdiFullscreen as Fullscreen, + mdiHelpCircleOutline as HelpCircleOutline, + mdiHistory as History, + mdiLabel as Label, + mdiLabelOutline as LabelOutline, + mdiMagnify as Magnify, + mdiMinusBox as MinusBox, + mdiNotificationClearAll as NotificationClearAll, + mdiPlusBox as PlusBox, + mdiProgressWrench as ProgressWrench, + mdiReorderHorizontal as ReorderHorizontal, + mdiRestore as Restore, + mdiTableLarge as TableLarge, + mdiTune as Tune, + mdiLayersSearchOutline as LayersSearchOutline, + mdiCloseCircle as CloseCircle, + mdiCloseCircleOutline as CloseCircleOutline, + mdiWall as Wall, + mdiTextBoxOutline as TextBoxOutline, + mdiPencil as Pencil, + mdiChevronDoubleUp as DoubleChevronUp, +} from "@mdi/js"; -const ApplicationBracesOutline = `M21 2H3C1.9 2 1 2.9 1 4V20C1 21.1 1.9 22 3 22H21C22.1 22 23 21.1 23 20V4C23 2.9 22.1 2 21 2M21 20H3V6H21V20M9 8C7.9 8 7 8.9 7 10C7 11.1 6.1 12 5 12V14C6.1 14 7 14.9 7 16C7 17.1 7.9 18 9 18H11V16H9V15C9 13.9 8.1 13 7 13C8.1 13 9 12.1 9 11V10H11V8M15 8C16.1 8 17 8.9 17 10C17 11.1 17.9 12 19 12V14C17.9 14 17 14.9 17 16C17 17.1 16.1 18 15 18H13V16H15V15C15 13.9 15.9 13 17 13C15.9 13 15 12.1 15 11V10H13V8H15Z` +const ApplicationBracesOutline = `M21 2H3C1.9 2 1 2.9 1 4V20C1 21.1 1.9 22 3 22H21C22.1 22 23 21.1 23 20V4C23 2.9 22.1 2 21 2M21 20H3V6H21V20M9 8C7.9 8 7 8.9 7 10C7 11.1 6.1 12 5 12V14C6.1 14 7 14.9 7 16C7 17.1 7.9 18 9 18H11V16H9V15C9 13.9 8.1 13 7 13C8.1 13 9 12.1 9 11V10H11V8M15 8C16.1 8 17 8.9 17 10C17 11.1 17.9 12 19 12V14C17.9 14 17 14.9 17 16C17 17.1 16.1 18 15 18H13V16H15V15C15 13.9 15.9 13 17 13C15.9 13 15 12.1 15 11V10H13V8H15Z`; const CircleOneThird = "M12 12 V2 A10 10 0 0 0 3.858 17.806 Z"; const CircleTwoThird = "M12 12 V2 A10 10 0 1 0 20.142 17.806 Z"; -const SavePDB = "M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h14Zm0 8v-.8c0-.7-.6-1.2-1.3-1.2h-2.4v6h2.4c.7 0 1.2-.5 1.2-1.2v-1c0-.4-.4-.8-.9-.8.5 0 1-.4 1-1Zm-9.7.5v-1c0-.8-.7-1.5-1.5-1.5H5.3v6h1.5v-2h1c.8 0 1.5-.7 1.5-1.5Zm5 2v-3c0-.8-.7-1.5-1.5-1.5h-2.5v6h2.5c.8 0 1.5-.7 1.5-1.5Zm3.4.3h-1.2v-1.2h1.2v1.2Zm-5.9-3.3v3h1v-3h-1Zm-5 0v1h1v-1h-1Zm11 .9h-1.3v-1.2h1.2v1.2Z"; -const SavePNG = "M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M9 11.5C9 12.3 8.3 13 7.5 13H6.5V15H5V9H7.5C8.3 9 9 9.7 9 10.5V11.5M14 15H12.5L11.5 12.5V15H10V9H11.5L12.5 11.5V9H14V15M19 10.5H16.5V13.5H17.5V12H19V13.7C19 14.4 18.5 15 17.7 15H16.4C15.6 15 15.1 14.3 15.1 13.7V10.4C15 9.7 15.5 9 16.3 9H17.6C18.4 9 18.9 9.7 18.9 10.3V10.5H19M6.5 10.5H7.5V11.5H6.5V10.5Z"; -const API = "M 22.23 1.96 c -0.98 0 -1.77 0.8 -1.77 1.77 c 0 0.21 0.05 0.4 0.12 0.6 l -8.31 14.23 c -0.8 0.17 -1.42 0.85 -1.42 1.7 a 1.77 1.77 0 0 0 3.54 0 c 0 -0.2 -0.05 -0.37 -0.1 -0.55 l 8.34 -14.29 a 1.75 1.75 0 0 0 1.37 -1.69 c 0 -0.97 -0.8 -1.77 -1.77 -1.77 M 14.98 1.96 c -0.98 0 -1.77 0.8 -1.77 1.77 c 0 0.21 0.05 0.4 0.12 0.6 l -8.3 14.24 c -0.81 0.16 -1.43 0.84 -1.43 1.7 a 1.77 1.77 0 0 0 3.55 0 c 0 -0.2 -0.06 -0.38 -0.12 -0.56 L 15.4 5.42 a 1.75 1.75 0 0 0 1.37 -1.69 c 0 -0.97 -0.8 -1.77 -1.78 -1.77 M 1.75 6 a 1.75 1.75 0 1 0 0 3.5 a 1.75 1.75 0 0 0 0 -3.5 z m 0 6 a 1.75 1.75 0 1 0 0 3.5 a 1.75 1.75 0 0 0 0 -3.5 z" -const Monomer = "m13.9 4.4.8.5a7.7 7.7 90 0 0 1.3.6 2.3 2.3 90 0 1 .5.2l.5.2.4.2.1.2a.4.4 90 0 1-.1.3A6.2 6.2 90 0 1 16 7a11.3 11.3 0 0 0-1.2.6l-.5-.2-1.1-.6a2 2 0 0 1-.5-.6 1.8 1.8 90 0 1-.2-1V3A5.3 5.3 90 0 0 14 4.4Zm-1.6-2c-.4-.8-1-1.3-1.6-1.3-1-.1-1.7.3-2.2 1.2a4.2 4.2 90 0 0-.3 1.3H10v12.9h2l-3.4 3.4.1 1.6c0 .4-.1.5-.4.5-.2 0 1.2-1-.4-.4L7.6 20l-3.3-3.4h1.9V3.6h1.1a7.4 7.4 90 0 1 .5-1.7C8.3.7 9.3.1 10.8.2a2.2 2.2 90 0 1 1.2.6A4.3 4.3 90 0 1 13 2v.3c0 .2 0 .4-.3.4-.2 0-.3 0-.4-.2Zm4.3 20.8a3 3 0 0 1-2.6-.5c-.8-.5-1.2-1.4-1.2-2.7 0-.3.1-.4.4-.4.3 0 .4.1.4.4 0 1 .3 1.7.8 2a2.5 2.5 90 0 0 2 .3h.1c.3 0 .5.2.5.5 0 .2-.1.3-.4.4Zm1.4-8v1a2.1 2.1 90 0 1-.3.5 2.6 2.6 90 0 1-.8.5l-.7.3-.6.3c-1.3.4-2 .7-2 .8a2.5 2.5 90 0 0-.8.5l-.2.3a3.3 3.3 90 0 1-.1-.8 5 5 90 0 1 0-1.3 2.4 2.4 90 0 1 .4-.8 3.6 3.6 90 0 1 .6-.4 38.4 38.4 0 0 1 4-1.9l.2-.3a2.6 2.6 90 0 1 .2.3 3.2 3.2 90 0 1 0 1Zm-5.2-1.8V13l.2-.4.7-.5 1-.3a7.7 7.7 90 0 0 1.3.6l.5.2.5.2.4.2.1.2-.1.2a2.4 2.4 90 0 1-.8.4 3 3 90 0 0-.5.2 1.9 1.9 90 0 1-.6.2l-.7.4-.5-.3a4.6 4.6 90 0 1-1.1-.5l-.2-.3-.2-.2ZM18 8.2a5.8 5.8 90 0 1 0 1 1.2 1.2 90 0 1-.3.5l-.5.3a189.3 189.3 90 0 0-3.6 1.5 2.5 2.5 90 0 0-.8.6l-.2.3-.1-.7v-1.3l.4-.9.5-.3L15.6 8l.6-.3a3.5 3.5 90 0 1 .5-.2l.9-.4.2-.3v.2l.2 1.1Z" -const Multimer = "M14 19.3c0-.3.2-.4.3-.5h.2c.3 0 .4.1.4.3.1.8.4 1.4 1 1.7.5.2 1 .3 1.5.1H17.7c.2-.1.4 0 .5.2 0 .3 0 .4-.3.5l-.4.2h-.3c-.5.1-1 0-1.7-.3-.4-.1-.7-.4-.9-.8a2.4 2.4 0 0 1-.4-1l-.1-.4Zm3.2-2h-.4a18.4 18.4 0 0 0-2.6.8l-.2.3v-.7c0-.5.1-.8.3-1l.6-.7.5-.3A63.2 63.2 0 0 1 18 15l.5-.2h.3l.3-.1.1-.1.3-.3v.2a7.5 7.5 0 0 1-.2 1.9l-.2.4-.8.4a15 15 0 0 0-.8.1h-.2Zm-5.1-.1v.6h-.6l-.4-.5-.6-.7-4.6-.8 1.5-1-5.9-8 .9-.7-.6-1.5c-.3-1.3.2-2.4 1.3-3.1l1.3-.3h.4l.4.1.2.1.6.4c.2 0 .3.2.2.5l-.4.2h-.2l-.4-.2-.8-.2c-.3 0-.6 0-.9.2a2 2 0 0 0-1 2.2l.6 1.1 1.3-1 1 1.4L5 8.6l1.4-1 3.8 5 1.5-1-.6 4.6.9 1Zm7-4-.1.3-.8.3a9 9 0 0 0-1.7.4 100.6 100.6 0 0 1-1.4-1.1v-.5c0-.1 0-.3.2-.4l.7-.3.8-.2.1.1.5.3.6.3a2092.6 2092.6 0 0 0 1.1.8Zm-8-2L7.2 6l-1.1.8.6-4 4 .7-1.1.8 4.2 5.7c-.4.5-.5 1.5-.4 3l-.6.4-.7-.9.2-2.2-1.2 1Zm9.2-3.7v1.2l-.2.8c0 .3-.2.4-.2.4l-.5.3a49.2 49.2 0 0 1-4.1 1l-.2.3V11c0-.4 0-.8.2-1.1l.5-.7.4-.3a19.2 19.2 0 0 1 4-1V7.4Zm-3.9-4.2c.3.5.6.9 1 1.1.2.4.5.6.6.6l.5.3a15.8 15.8 0 0 1 .8.6h.3l.1.2h.2c0 .2.2.3.3.3V7h-.5a2 2 0 0 0-.3.2l-.5.1-.5.1h-.1l-.3.1a1 1 0 0 0-.3 0l-.5-.2a4.7 4.7 0 0 1-1-1l-.2-.2V5l.4-2v.3Zm1.9-1.8c.1 0 .9-1.4 0-1.1-.9.2-1.5.7-1.8 1.5 0 .4 0 .7.5.8.4 0 .6-.1.7-.5 0-.3.3-.5.6-.7Z"; -const Motif = "m12.42 17.45.15 1.38a.8.8 0 0 0 .34.57l1.69 1.4a.8.8 0 0 0 1.02-1.23l-1.5-1.24-.14-1.3 1.79-2.67-2.53-3.22-3.84 1.4.16 4.1 2.86.81Zm.4-1.55-1.71-.48-.07-1.77 1.66-.61 1.1 1.39-.99 1.47Zm-.3-6.77a2.4 2.4 0 1 0-3.02 1.52h-.01a2.4 2.4 0 0 0 3.02-1.52Zm7.03-3.1-2.21-2.32-3.7 1.77.55 4.05 4.02.74 1.42-2.62L21 7.8a.8.8 0 0 0 .62-.2l1.74-1.34a.8.8 0 0 0-.98-1.27l-1.54 1.19-1.3-.14Zm-1.35.9-.84 1.56-1.74-.32-.24-1.75 1.6-.77 1.22 1.28ZM4.57.05.69.55A.8.8 0 0 0 .9 2.16l3.58-.48L6.96 5a.8.8 0 0 0 1.28-.95L5.61.5a.8.8 0 0 0-.54-.46.8.8 0 0 0-.5 0Z"; +const SavePDB = + "M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h14Zm0 8v-.8c0-.7-.6-1.2-1.3-1.2h-2.4v6h2.4c.7 0 1.2-.5 1.2-1.2v-1c0-.4-.4-.8-.9-.8.5 0 1-.4 1-1Zm-9.7.5v-1c0-.8-.7-1.5-1.5-1.5H5.3v6h1.5v-2h1c.8 0 1.5-.7 1.5-1.5Zm5 2v-3c0-.8-.7-1.5-1.5-1.5h-2.5v6h2.5c.8 0 1.5-.7 1.5-1.5Zm3.4.3h-1.2v-1.2h1.2v1.2Zm-5.9-3.3v3h1v-3h-1Zm-5 0v1h1v-1h-1Zm11 .9h-1.3v-1.2h1.2v1.2Z"; +const SavePNG = + "M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M9 11.5C9 12.3 8.3 13 7.5 13H6.5V15H5V9H7.5C8.3 9 9 9.7 9 10.5V11.5M14 15H12.5L11.5 12.5V15H10V9H11.5L12.5 11.5V9H14V15M19 10.5H16.5V13.5H17.5V12H19V13.7C19 14.4 18.5 15 17.7 15H16.4C15.6 15 15.1 14.3 15.1 13.7V10.4C15 9.7 15.5 9 16.3 9H17.6C18.4 9 18.9 9.7 18.9 10.3V10.5H19M6.5 10.5H7.5V11.5H6.5V10.5Z"; +const API = + "M 22.23 1.96 c -0.98 0 -1.77 0.8 -1.77 1.77 c 0 0.21 0.05 0.4 0.12 0.6 l -8.31 14.23 c -0.8 0.17 -1.42 0.85 -1.42 1.7 a 1.77 1.77 0 0 0 3.54 0 c 0 -0.2 -0.05 -0.37 -0.1 -0.55 l 8.34 -14.29 a 1.75 1.75 0 0 0 1.37 -1.69 c 0 -0.97 -0.8 -1.77 -1.77 -1.77 M 14.98 1.96 c -0.98 0 -1.77 0.8 -1.77 1.77 c 0 0.21 0.05 0.4 0.12 0.6 l -8.3 14.24 c -0.81 0.16 -1.43 0.84 -1.43 1.7 a 1.77 1.77 0 0 0 3.55 0 c 0 -0.2 -0.06 -0.38 -0.12 -0.56 L 15.4 5.42 a 1.75 1.75 0 0 0 1.37 -1.69 c 0 -0.97 -0.8 -1.77 -1.78 -1.77 M 1.75 6 a 1.75 1.75 0 1 0 0 3.5 a 1.75 1.75 0 0 0 0 -3.5 z m 0 6 a 1.75 1.75 0 1 0 0 3.5 a 1.75 1.75 0 0 0 0 -3.5 z"; +const Monomer = + "m13.9 4.4.8.5a7.7 7.7 90 0 0 1.3.6 2.3 2.3 90 0 1 .5.2l.5.2.4.2.1.2a.4.4 90 0 1-.1.3A6.2 6.2 90 0 1 16 7a11.3 11.3 0 0 0-1.2.6l-.5-.2-1.1-.6a2 2 0 0 1-.5-.6 1.8 1.8 90 0 1-.2-1V3A5.3 5.3 90 0 0 14 4.4Zm-1.6-2c-.4-.8-1-1.3-1.6-1.3-1-.1-1.7.3-2.2 1.2a4.2 4.2 90 0 0-.3 1.3H10v12.9h2l-3.4 3.4.1 1.6c0 .4-.1.5-.4.5-.2 0 1.2-1-.4-.4L7.6 20l-3.3-3.4h1.9V3.6h1.1a7.4 7.4 90 0 1 .5-1.7C8.3.7 9.3.1 10.8.2a2.2 2.2 90 0 1 1.2.6A4.3 4.3 90 0 1 13 2v.3c0 .2 0 .4-.3.4-.2 0-.3 0-.4-.2Zm4.3 20.8a3 3 0 0 1-2.6-.5c-.8-.5-1.2-1.4-1.2-2.7 0-.3.1-.4.4-.4.3 0 .4.1.4.4 0 1 .3 1.7.8 2a2.5 2.5 90 0 0 2 .3h.1c.3 0 .5.2.5.5 0 .2-.1.3-.4.4Zm1.4-8v1a2.1 2.1 90 0 1-.3.5 2.6 2.6 90 0 1-.8.5l-.7.3-.6.3c-1.3.4-2 .7-2 .8a2.5 2.5 90 0 0-.8.5l-.2.3a3.3 3.3 90 0 1-.1-.8 5 5 90 0 1 0-1.3 2.4 2.4 90 0 1 .4-.8 3.6 3.6 90 0 1 .6-.4 38.4 38.4 0 0 1 4-1.9l.2-.3a2.6 2.6 90 0 1 .2.3 3.2 3.2 90 0 1 0 1Zm-5.2-1.8V13l.2-.4.7-.5 1-.3a7.7 7.7 90 0 0 1.3.6l.5.2.5.2.4.2.1.2-.1.2a2.4 2.4 90 0 1-.8.4 3 3 90 0 0-.5.2 1.9 1.9 90 0 1-.6.2l-.7.4-.5-.3a4.6 4.6 90 0 1-1.1-.5l-.2-.3-.2-.2ZM18 8.2a5.8 5.8 90 0 1 0 1 1.2 1.2 90 0 1-.3.5l-.5.3a189.3 189.3 90 0 0-3.6 1.5 2.5 2.5 90 0 0-.8.6l-.2.3-.1-.7v-1.3l.4-.9.5-.3L15.6 8l.6-.3a3.5 3.5 90 0 1 .5-.2l.9-.4.2-.3v.2l.2 1.1Z"; +const Multimer = + "M14 19.3c0-.3.2-.4.3-.5h.2c.3 0 .4.1.4.3.1.8.4 1.4 1 1.7.5.2 1 .3 1.5.1H17.7c.2-.1.4 0 .5.2 0 .3 0 .4-.3.5l-.4.2h-.3c-.5.1-1 0-1.7-.3-.4-.1-.7-.4-.9-.8a2.4 2.4 0 0 1-.4-1l-.1-.4Zm3.2-2h-.4a18.4 18.4 0 0 0-2.6.8l-.2.3v-.7c0-.5.1-.8.3-1l.6-.7.5-.3A63.2 63.2 0 0 1 18 15l.5-.2h.3l.3-.1.1-.1.3-.3v.2a7.5 7.5 0 0 1-.2 1.9l-.2.4-.8.4a15 15 0 0 0-.8.1h-.2Zm-5.1-.1v.6h-.6l-.4-.5-.6-.7-4.6-.8 1.5-1-5.9-8 .9-.7-.6-1.5c-.3-1.3.2-2.4 1.3-3.1l1.3-.3h.4l.4.1.2.1.6.4c.2 0 .3.2.2.5l-.4.2h-.2l-.4-.2-.8-.2c-.3 0-.6 0-.9.2a2 2 0 0 0-1 2.2l.6 1.1 1.3-1 1 1.4L5 8.6l1.4-1 3.8 5 1.5-1-.6 4.6.9 1Zm7-4-.1.3-.8.3a9 9 0 0 0-1.7.4 100.6 100.6 0 0 1-1.4-1.1v-.5c0-.1 0-.3.2-.4l.7-.3.8-.2.1.1.5.3.6.3a2092.6 2092.6 0 0 0 1.1.8Zm-8-2L7.2 6l-1.1.8.6-4 4 .7-1.1.8 4.2 5.7c-.4.5-.5 1.5-.4 3l-.6.4-.7-.9.2-2.2-1.2 1Zm9.2-3.7v1.2l-.2.8c0 .3-.2.4-.2.4l-.5.3a49.2 49.2 0 0 1-4.1 1l-.2.3V11c0-.4 0-.8.2-1.1l.5-.7.4-.3a19.2 19.2 0 0 1 4-1V7.4Zm-3.9-4.2c.3.5.6.9 1 1.1.2.4.5.6.6.6l.5.3a15.8 15.8 0 0 1 .8.6h.3l.1.2h.2c0 .2.2.3.3.3V7h-.5a2 2 0 0 0-.3.2l-.5.1-.5.1h-.1l-.3.1a1 1 0 0 0-.3 0l-.5-.2a4.7 4.7 0 0 1-1-1l-.2-.2V5l.4-2v.3Zm1.9-1.8c.1 0 .9-1.4 0-1.1-.9.2-1.5.7-1.8 1.5 0 .4 0 .7.5.8.4 0 .6-.1.7-.5 0-.3.3-.5.6-.7Z"; +const Motif = + "m12.42 17.45.15 1.38a.8.8 0 0 0 .34.57l1.69 1.4a.8.8 0 0 0 1.02-1.23l-1.5-1.24-.14-1.3 1.79-2.67-2.53-3.22-3.84 1.4.16 4.1 2.86.81Zm.4-1.55-1.71-.48-.07-1.77 1.66-.61 1.1 1.39-.99 1.47Zm-.3-6.77a2.4 2.4 0 1 0-3.02 1.52h-.01a2.4 2.4 0 0 0 3.02-1.52Zm7.03-3.1-2.21-2.32-3.7 1.77.55 4.05 4.02.74 1.42-2.62L21 7.8a.8.8 0 0 0 .62-.2l1.74-1.34a.8.8 0 0 0-.98-1.27l-1.54 1.19-1.3-.14Zm-1.35.9-.84 1.56-1.74-.32-.24-1.75 1.6-.77 1.22 1.28ZM4.57.05.69.55A.8.8 0 0 0 .9 2.16l3.58-.48L6.96 5a.8.8 0 0 0 1.28-.95L5.61.5a.8.8 0 0 0-.54-.46.8.8 0 0 0-.5 0Z"; export default { - AlertCircleOutline, - ApplicationBracesOutline, - ArrowRightCircle, - ArrowRightCircleOutline, - AxisZRotateCounterclockwise, - ChevronLeft, - ChevronRight, - Circle, - CircleHalf, - CircleOneThird, - CircleTwoThird, - ClockOutline, - CloudDownloadOutline, - Delete, - Dns, - File, - FileDownloadOutline, - FileUpload, - FormatListBulleted, - Fullscreen, - HelpCircleOutline, - History, - Label, - LabelOutline, - Magnify, - MinusBox, - NotificationClearAll, - PlusBox, - ProgressWrench, - ReorderHorizontal, - Restore, - SavePDB, - SavePNG, - TableLarge, - Tune, - LayersSearchOutline, - API, - CloseCircle, - CloseCircleOutline, - Monomer, - Multimer, - Wall, - TextBoxOutline, - Motif, - Pencil, -}; \ No newline at end of file + AlertCircleOutline, + ApplicationBracesOutline, + ArrowRightCircle, + ArrowRightCircleOutline, + AxisZRotateCounterclockwise, + ChevronLeft, + ChevronRight, + ChevronUp, + ChevronDown, + Circle, + CircleHalf, + CircleOneThird, + CircleTwoThird, + ClockOutline, + CloudDownloadOutline, + Delete, + Dns, + File, + FileDownloadOutline, + FileUpload, + FormatListBulleted, + Fullscreen, + HelpCircleOutline, + History, + Label, + LabelOutline, + Magnify, + MinusBox, + NotificationClearAll, + PlusBox, + ProgressWrench, + ReorderHorizontal, + Restore, + SavePDB, + SavePNG, + TableLarge, + Tune, + LayersSearchOutline, + API, + CloseCircle, + CloseCircleOutline, + Monomer, + Multimer, + Wall, + TextBoxOutline, + Motif, + Pencil, + DoubleChevronUp, +}; diff --git a/frontend/ResultFoldDisco.vue b/frontend/ResultFoldDisco.vue index 6e99862..35fc3fa 100644 --- a/frontend/ResultFoldDisco.vue +++ b/frontend/ResultFoldDisco.vue @@ -78,173 +78,67 @@ - + - All databases - {{ entry.db.replaceAll(/_folddisco$/g, '') }} ({{ entry.alignments ? Object.values(entry.alignments).length : 0 }}) - -
- -

- {{ entry.db.replaceAll(/_folddisco$/g, '') }} {{ entry.alignments ? Object.values(entry.alignments).length : 0 }} hits -

- - {{ isSankeyVisible[entry.db] ? 'Hide Taxonomy' : 'Show Taxonomy' }} - -
- - -
-

Filter

- - -
-
-

Cluster

- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ void(clusterShown = false) }} - - -
- Target - - Description - - - Triple click to select whole cell (for very long identifiers) - - Scientific Nameidf-scoreRMSDNodes - Matched residues - - - The position of the aligned motif residues in the target - - Structure
-
+ + {{ entry.db.replaceAll(/_folddisco$/g, '') }} ({{ entry.alignments ? Object.values(entry.alignments).length : 0 }}) +
+ + + + + - + + @@ -608,6 +753,7 @@ export default { text-overflow: ellipsis; white-space: nowrap; } + } } @@ -702,6 +848,7 @@ export default { word-break: break-all; max-width: none; } + } .alignment { @@ -722,6 +869,25 @@ export default { } } } + +.sticky-tabs::before { + content: ""; + width: 100%; + position: absolute; + top: -16px; + background-color: inherit; + display: block; + height: 16px; + z-index: inherit; +} + +.collapse-icon:not(.collapsed) { + transform: rotate(90deg); +} + +.collapse-icon { + cursor: pointer; +} \ No newline at end of file diff --git a/frontend/ResultFoldMason.vue b/frontend/ResultFoldMason.vue index 99d02da..9c735b7 100644 --- a/frontend/ResultFoldMason.vue +++ b/frontend/ResultFoldMason.vue @@ -66,6 +66,7 @@ + @@ -77,16 +78,17 @@ import makeZip from './lib/zip.js' import MSA from './MSA.vue'; import MSAView from './MSAView.vue'; import Panel from './Panel.vue'; +import NavigationButton from './NavigationButton.vue'; export default { name: 'ResultFoldMason', tool: 'foldmason', - components: { MSA, MSAView, Panel }, + components: { MSA, MSAView, Panel, NavigationButton}, data() { return { ticket: "", error: "", - msaData: null + msaData: null, } }, mounted() { diff --git a/frontend/ResultFoldseekDB.vue b/frontend/ResultFoldseekDB.vue new file mode 100644 index 0000000..166085b --- /dev/null +++ b/frontend/ResultFoldseekDB.vue @@ -0,0 +1,1035 @@ + + + + + \ No newline at end of file diff --git a/frontend/ResultView.vue b/frontend/ResultView.vue index 6d30936..d4c79c6 100644 --- a/frontend/ResultView.vue +++ b/frontend/ResultView.vue @@ -70,7 +70,7 @@ rel="noopener" > - {{ item.label }} + {{ item .label }} {{ item.accession }} @@ -78,174 +78,73 @@ - + + - All databases + > + {{ entry.db }} ({{ entry.alignments ? Object.values(entry.alignments).length : 0 }}) -
- -

- {{ entry.db }} {{ entry.alignments ? Object.values(entry.alignments).length : 0 }} hits -

- - - - {{ isSankeyVisible[entry.db] ? 'Hide Taxonomy' : 'Show Taxonomy' }} - - - - - Graphical - - - - Numeric - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ComplexChain
- - - - Description - - - Triple click to select whole cell (for very long identifiers) - - Scientific NameProb.Seq. Id.{{ scoreColumnName }}ScoreQuery Pos.Target Pos. - Position in query - - - The position of the aligned region of the target sequence in the query - - Alignment
-
+ +
+ + + - + + [i, Object.fromEntries( + e.alignments.map((_, j) => [j, false]) + )]) + ) + const obj2 = Object.fromEntries( + n.results.map(( e,i ) => [i, 0]) + ) + const obj3 = Object.fromEntries( + n.results.map((e, i) => [e.db, i]) + ) + this.selectedStates = obj + this.selectedCountPerDb = obj2 + this.dbToIdx = obj3 + this.$nextTick(() => { + setTimeout(() => { + this.updateScrollOffsetArr() + }, 0) + }) + } }, beforeDestroy() { window.removeEventListener("resize", this.handleAlignmentBoxResize); }, + watch: { + hits: { + handler(n, o) { + if (n && n.results) { + const obj = Object.fromEntries( + n.results.map((e, i) => [i, Object.fromEntries( + [...Array(e.alignments.length)].keys().map(j => [j, false]) + )]) + ) + const obj2 = Object.fromEntries( + n.results.map((e, i) => [i, 0]) + ) + const obj3 = Object.fromEntries( + n.results.map((e, i) => [e.db, i]) + ) + this.selectedStates = obj + this.selectedCountPerDb = obj2 + this.dbToIdx = obj3 + this.$nextTick(() => { + setTimeout(() => { + this.updateScrollOffsetArr() + }, 0) + }) + } + }, + immediate: false, + deep: true, + }, + }, computed: { mode() { return this.hits?.mode ?? ""; @@ -314,9 +279,9 @@ export default { if (this.$vuetify.breakpoint.xsOnly) { return 30; } else if (this.$vuetify.breakpoint.smAndDown) { - return 45; - } else if (this.$vuetify.breakpoint.mdAndDown) { return 60; + } else if (this.$vuetify.breakpoint.mdAndDown) { + return 45; } else { return 80; } @@ -342,17 +307,16 @@ export default { return "ERROR"; }, - scoreColumnName() { - console.log(this.mode) - if (__APP__ == 'foldseek') { - switch (this.mode) { - case 'tmalign': - return 'TM-score'; - case 'lolalign': - return 'LoL-score'; - } + onlyOne() { + return this.hits?.results?.length == 1 + }, + tabModel: { + get() { + return this.selectedDatabases - 1; + }, + set(val) { + this.selectedDatabases = val + 1; } - return 'E-Value'; }, }, methods: { @@ -373,9 +337,14 @@ export default { }); } }, + switchTableMode(value) { + this.tableMode = value + }, closeAlignment() { - this.alignment = null; - this.activeTarget = null; + this.$nextTick(() => { + this.alignment = null; + this.activeTarget = null; + }) }, handleAlignmentBoxResize: debounce(function() { if (this.activeTarget != null) { @@ -390,194 +359,290 @@ export default { }, handleChangeDatabase() { this.closeAlignment(); - this.localSelectedTaxId = null; - this.filteredHitsTaxIds = []; }, - isGroupVisible(group) { - if (!this.filteredHitsTaxIds || this.filteredHitsTaxIds.length === 0) { - return true; + handleToggleSelection(db, idx, value) { + if (!this.selectedStates || !this.selectedStates[db] + || this.selectedCounts > this.selectUpperbound && value) { + return + } + + const id = db + '#' + idx.toString() + const deltaUnit = value ? 1 : -1 + const toCall = value ? this.selectedSets.add.bind(this.selectedSets) : + this.selectedSets.delete.bind(this.selectedSets) + if (this.selectedStates[db][idx] != value) { + // Does it really reflect changes? + this.selectedStates[db][idx] = value + let el = document.getElementById(id) + if (el) { + el.classList.toggle('selected', value) + } + const newVal = this.selectedCountPerDb[db] + deltaUnit + this.selectedCountPerDb[db] = newVal + this.selectedCounts += deltaUnit + toCall(id) + + // update select-all button state + const targetDbLength = this.selectedStates[db].length + el = document.getElementById(db + '#select-all') + if (el) { + el.classList.toggle('any-selected', newVal > 0) + el.classList.toggle('all-selected', newVal == targetDbLength) + } } - let taxFiltered = group.filter(item => this.filteredHitsTaxIds.includes(Number(item.taxId))); - return taxFiltered.length > 0; }, - } -}; - - - \ No newline at end of file diff --git a/frontend/StructureViewer.vue b/frontend/StructureViewer.vue index cb2259a..b8190c8 100644 --- a/frontend/StructureViewer.vue +++ b/frontend/StructureViewer.vue @@ -38,7 +38,7 @@ import StructureViewerTooltip from './StructureViewerTooltip.vue'; import StructureViewerToolbar from './StructureViewerToolbar.vue'; import StructureViewerMixin from './StructureViewerMixin.vue'; -import { mockPDB, makeSubPDB, transformStructure, makeMatrix4 } from './Utilities.js'; +import { mockPDB, makeSubPDB, transformStructure, makeMatrix4, storeChains, revertChainInfo } from './Utilities.js'; import { pulchra } from 'pulchra-wasm'; import { tmalign, parse as parseTMOutput, parseMatrix as parseTMMatrix } from 'tmalign-wasm'; @@ -164,6 +164,7 @@ export default { methods: { // Create arrows connecting CA coordinates for query/target in match columns async drawArrows(str1, str2) { + if (!this.stage) return; const shape = new Shape('arrows'); await Promise.all(this.alignments.map(async (alignment) => { const chain_q = getChainName(alignment.query); @@ -236,6 +237,7 @@ export default { repr.setVisibility(true); }, setQuerySelection() { + if (!this.stage) return; let repr = this.stage.getRepresentationsByName("queryStructure"); if (!repr) return; let sele = this.querySele; @@ -253,6 +255,7 @@ export default { } }, setTargetSelection() { + if (!this.stage) return; let repr = this.stage.getRepresentationsByName("targetStructure"); if (!repr) return; let sele = this.targetSele; @@ -403,30 +406,35 @@ END let lastIdx = null; let remoteData = null; let i = 0; - for (let alignment of this.alignments) { - const chain = getChainName(alignment.target); - let tSeq = alignment.tSeq; - let tCa = alignment.tCa; - if (Number.isInteger(alignment.tCa) && Number.isInteger(alignment.tSeq)) { - const db = alignment.db; - const idx = alignment.tCa; - if (idx != lastIdx) { - const ticket = this.$route.params.ticket; - const response = await this.$axios.get("api/result/" + ticket + '/' + this.$route.params.entry + '?format=brief&index=' + idx + '&database=' + db); - remoteData = response.data; - lastIdx = idx; + // It is wrapped in order to make it handle when it is destroyed even before it is fully mounted + try { + for (let alignment of this.alignments) { + const chain = getChainName(alignment.target); + let tSeq = alignment.tSeq; + let tCa = alignment.tCa; + if (Number.isInteger(alignment.tCa) && Number.isInteger(alignment.tSeq)) { + const db = alignment.db; + const idx = alignment.tCa; + if (idx != lastIdx) { + const ticket = this.$route.params.ticket; + const response = await this.$axios.get("api/result/" + ticket + '/' + this.$route.params.entry + '?format=brief&index=' + idx + '&database=' + db); + remoteData = response.data; + lastIdx = idx; + } + tSeq = remoteData[i].tSeq; + tCa = remoteData[i].tCa; + i++; } - tSeq = remoteData[i].tSeq; - tCa = remoteData[i].tCa; - i++; + const mock = mockPDB(tCa, tSeq, chain); + const pdb = await pulchra(mock); + const component = await this.stage.loadFile(new Blob([pdb], { type: 'text/plain' }), {ext: 'pdb', firstModelOnly: true}); + component.structure.eachChain(c => { c.chainname = chain; }); + component.structure.eachAtom(a => { a.serial = renumber++; }); + targets.push(component); + selections_t.push(`${alignment.dbStartPos}-${alignment.dbEndPos}:${chain}`); } - const mock = mockPDB(tCa, tSeq, chain); - const pdb = await pulchra(mock); - const component = await this.stage.loadFile(new Blob([pdb], { type: 'text/plain' }), {ext: 'pdb', firstModelOnly: true}); - component.structure.eachChain(c => { c.chainname = chain; }); - component.structure.eachAtom(a => { a.serial = renumber++; }); - targets.push(component); - selections_t.push(`${alignment.dbStartPos}-${alignment.dbEndPos}:${chain}`); + } catch (e) { + return } const structure = concatStructures(getAccession(this.alignments[0].target), ...targets.map(t => t.structure)); const target = this.stage.addComponentFromObject(structure, { name: "targetStructure" }); @@ -471,7 +479,11 @@ END // FIXME: pulchra probably should learn mmCIF queryPdb = getPdbText(query); } + // As pulchra loses the chain information, it could result in mismatch between selection and pulchra-generated pdb + // So we should store chain information of pdb structure information and recover it + const chains = storeChains(queryPdb) queryPdb = await pulchra(queryPdb); + queryPdb = revertChainInfo(queryPdb, chains) this.stage.removeComponent(query); query = await this.stage.loadFile(new Blob([queryPdb], { type: 'text/plain' }), {ext: 'pdb', firstModelOnly: true, name: 'queryStructure'}); } diff --git a/frontend/Utilities.js b/frontend/Utilities.js index d2c5e5a..0fc05be 100644 --- a/frontend/Utilities.js +++ b/frontend/Utilities.js @@ -1,313 +1,392 @@ -import { Selection, Matrix4, PdbWriter } from 'ngl'; +import { Selection, Matrix4, PdbWriter } from "ngl"; function tryLinkTargetToDB(target, db) { - try { - var res = db.toLowerCase(); - if (res.startsWith("pfam")) { - return 'https://www.ebi.ac.uk/interpro/entry/pfam/' + target; - } else if (res.startsWith("pdb")) { - return 'https://www.rcsb.org/structure/' + target.replaceAll(/-assembly[0-9]+/g, '').replaceAll(/\.(cif|pdb|ent)(\.gz)?/g, '').split('_')[0]; - } else if (res.startsWith("uniclust") || res.startsWith("uniprot") || res.startsWith("sprot") || res.startsWith("swissprot")) { - return 'https://www.uniprot.org/uniprot/' + target; - } else if (res.startsWith("eggnog_")) { - return 'http://eggnogdb.embl.de/#/app/results?target_nogs=' + target; - } else if (res.startsWith("cdd")) { - return 'https://www.ncbi.nlm.nih.gov/Structure/cdd/cddsrv.cgi?uid=' + target; - } + try { + var res = db.toLowerCase(); + if (res.startsWith("pfam")) { + return "https://www.ebi.ac.uk/interpro/entry/pfam/" + target; + } else if (res.startsWith("pdb")) { + return ( + "https://www.rcsb.org/structure/" + + target + .replaceAll(/-assembly[0-9]+/g, "") + .replaceAll(/\.(cif|pdb|ent)(\.gz)?/g, "") + .split("_")[0] + ); + } else if ( + res.startsWith("uniclust") || + res.startsWith("uniprot") || + res.startsWith("sprot") || + res.startsWith("swissprot") + ) { + return "https://www.uniprot.org/uniprot/" + target; + } else if (res.startsWith("eggnog_")) { + return "http://eggnogdb.embl.de/#/app/results?target_nogs=" + target; + } else if (res.startsWith("cdd")) { + return ( + "https://www.ncbi.nlm.nih.gov/Structure/cdd/cddsrv.cgi?uid=" + target + ); + } - if (__APP__ == "foldseek") { - if (target.startsWith("AF-")) { - let accession = target.replaceAll(/-F[0-9]+-model_v[0-9]+(\.(cif|pdb))?(\.gz)?(_[A-Z0-9]+)?$/g, ''); - accession = accession.substring(3); - return [ - { label: 'AFDB', accession: accession, href:'https://www.alphafold.ebi.ac.uk/entry/' + accession }, - { label: 'UniProt', accession: accession, href: 'https://www.uniprot.org/uniprot/' + accession }, - ]; - } else if (target.startsWith("GMGC")) { - return 'https://gmgc.embl.de/search.cgi?search_id=' + target.replaceAll(/\.(cif|pdb)(\.gz)?/g, '') - } else if (target.startsWith("MGYP")) { - return 'https://esmatlas.com/explore/detail/' + target.replaceAll(/\.(cif|pdb)(\.gz)?/g, '') - } else if (target.startsWith("LevyLab_")) { - let accession = target.split('_')[1]; - return [ - { label: 'AFDB', accession: accession, href:'https://www.alphafold.ebi.ac.uk/entry/' + accession }, - { label: 'UniProt', accession: accession, href: 'https://www.uniprot.org/uniprot/' + accession }, - ]; - } else if ( target.startsWith("ProtVar_")) { - let accession1 = target.split('_')[1]; - let accession2 = target.split('_')[2]; - let result = [ - { label: 'AFDB', accession: accession1, href:'https://www.alphafold.ebi.ac.uk/entry/' + accession1 }, - { label: 'UniProt', accession: accession1, href: 'https://www.uniprot.org/uniprot/' + accession1 }, - ]; - if (accession1 != accession2) { - result.push({ label: 'AFDB', accession: accession2, href:'https://www.alphafold.ebi.ac.uk/entry/' + accession2 }); - result.push({ label: 'UniProt', accession: accession2, href: 'https://www.uniprot.org/uniprot/' + accession2 }); - } - return result; - } else if (target.startsWith("ModelArchive_")) { - return 'https://modelarchive.org/doi/10.5452/' + target.split('_')[1]; - } else if (target.startsWith("Predictome_")) { - return 'https://predictomes.org/summary/' + target.split('_')[1]; - } - if (res.startsWith("cath")) { - if (target.startsWith('af_')) { - const cath = target.substring(target.lastIndexOf('_') + 1); - return "https://www.cathdb.info/version/latest/superfamily/" + cath; - } else { - return "https://www.cathdb.info/version/latest/domain/"+ target; - } - } else if (res.startsWith("bfvd")) { - const bfvd = target.replaceAll(/_.*/g, ''); - return [ - { label: "BFVD", accession: bfvd, href: "https://bfvd.foldseek.com/cluster/" + bfvd }, - { label: "UniRef", accession: bfvd, href: "https://www.uniprot.org/uniref/UniRef100_" + bfvd }, - ] - } + if (__APP__ == "foldseek") { + if (target.startsWith("AF-")) { + let accession = target.replaceAll( + /-F[0-9]+-model_v[0-9]+(\.(cif|pdb))?(\.gz)?(_[A-Z0-9]+)?$/g, + "" + ); + accession = accession.substring(3); + return [ + { + label: "AFDB", + accession: accession, + href: "https://www.alphafold.ebi.ac.uk/entry/" + accession, + }, + { + label: "UniProt", + accession: accession, + href: "https://www.uniprot.org/uniprot/" + accession, + }, + ]; + } else if (target.startsWith("GMGC")) { + return ( + "https://gmgc.embl.de/search.cgi?search_id=" + + target.replaceAll(/\.(cif|pdb)(\.gz)?/g, "") + ); + } else if (target.startsWith("MGYP")) { + return ( + "https://esmatlas.com/explore/detail/" + + target.replaceAll(/\.(cif|pdb)(\.gz)?/g, "") + ); + } else if (target.startsWith("LevyLab_")) { + let accession = target.split("_")[1]; + return [ + { + label: "AFDB", + accession: accession, + href: "https://www.alphafold.ebi.ac.uk/entry/" + accession, + }, + { + label: "UniProt", + accession: accession, + href: "https://www.uniprot.org/uniprot/" + accession, + }, + ]; + } else if (target.startsWith("ProtVar_")) { + let accession1 = target.split("_")[1]; + let accession2 = target.split("_")[2]; + let result = [ + { + label: "AFDB", + accession: accession1, + href: "https://www.alphafold.ebi.ac.uk/entry/" + accession1, + }, + { + label: "UniProt", + accession: accession1, + href: "https://www.uniprot.org/uniprot/" + accession1, + }, + ]; + if (accession1 != accession2) { + result.push({ + label: "AFDB", + accession: accession2, + href: "https://www.alphafold.ebi.ac.uk/entry/" + accession2, + }); + result.push({ + label: "UniProt", + accession: accession2, + href: "https://www.uniprot.org/uniprot/" + accession2, + }); } - return null; - } catch (e) { - return null; + return result; + } else if (target.startsWith("ModelArchive_")) { + return "https://modelarchive.org/doi/10.5452/" + target.split("_")[1]; + } else if (target.startsWith("Predictome_")) { + return "https://predictomes.org/summary/" + target.split("_")[1]; + } + if (res.startsWith("cath")) { + if (target.startsWith("af_")) { + const cath = target.substring(target.lastIndexOf("_") + 1); + return "https://www.cathdb.info/version/latest/superfamily/" + cath; + } else { + return "https://www.cathdb.info/version/latest/domain/" + target; + } + } else if (res.startsWith("bfvd")) { + const bfvd = target.replaceAll(/_.*/g, ""); + return [ + { + label: "BFVD", + accession: bfvd, + href: "https://bfvd.foldseek.com/cluster/" + bfvd, + }, + { + label: "UniRef", + accession: bfvd, + href: "https://www.uniprot.org/uniref/UniRef100_" + bfvd, + }, + ]; + } } + return null; + } catch (e) { + return null; + } } function tryFixTargetName(target, db) { - var res = db.toLowerCase(); - if (__APP__ == "foldseek") { - if (target.startsWith("AF-")) { - return target.replaceAll(/\.(cif|pdb)(\.gz)?(_[A-Z0-9]+)?$/g, ''); - } else if (res.startsWith("pdb") || res.startsWith("gmgc") || res.startsWith("mgyp") || res.startsWith("mgnify")) { - return target.replaceAll(/\.(cif|pdb|ent)(\.gz)?/g, ''); - } else if (res.startsWith("bfvd")) { - return target.replaceAll(/_unrelaxed.*/g, ''); - } - if (res.startsWith("cath")) { - if (target.startsWith('af_')) { - const match = target.match(/^af_([A-Z0-9]+)_(\d+)_(\d+)_(\d+\.\d+\.\d+\.\d+)$/); - if (match && match.length == 5) { - return match[4] + ' ' + match[1] + ' ' + match[2] + '-' + match[3]; - } - } + var res = db.toLowerCase(); + if (__APP__ == "foldseek") { + if (target.startsWith("AF-")) { + return target.replaceAll(/\.(cif|pdb)(\.gz)?(_[A-Z0-9]+)?$/g, ""); + } else if ( + res.startsWith("pdb") || + res.startsWith("gmgc") || + res.startsWith("mgyp") || + res.startsWith("mgnify") + ) { + return target.replaceAll(/\.(cif|pdb|ent)(\.gz)?/g, ""); + } else if (res.startsWith("bfvd")) { + return target.replaceAll(/_unrelaxed.*/g, ""); + } + if (res.startsWith("cath")) { + if (target.startsWith("af_")) { + const match = target.match( + /^af_([A-Z0-9]+)_(\d+)_(\d+)_(\d+\.\d+\.\d+\.\d+)$/ + ); + if (match && match.length == 5) { + return match[4] + " " + match[1] + " " + match[2] + "-" + match[3]; } + } } - return target; + } + return target; } // Process e.g. AF-{uniprot ID}-F1_model_v4.cif.pdb.gz to just uniprot ID export function tryFixName(name) { - if (name.startsWith("AF-")) { - name = name.replaceAll(/(AF[-_]|[-_]F[0-9]+[-_]model[-_]v[0-9]+)/g, '') - } - return name.replaceAll(/\.(cif|pdb|gz)/g, ''); + if (name.startsWith("AF-")) { + name = name.replaceAll(/(AF[-_]|[-_]F[0-9]+[-_]model[-_]v[0-9]+)/g, ""); + } + return name.replaceAll(/\.(cif|pdb|gz)/g, ""); } export function parseResults(data) { - let empty = 0; - let total = 0; - for (let i in data.results) { - let result = data.results[i]; - let db = result.db; - result.hasDescription = false; - result.hasTaxonomy = false; - if (result.alignments == null) { - empty++; + let empty = 0; + let total = 0; + for (let i in data.results) { + let result = data.results[i]; + let db = result.db; + result.hasDescription = false; + result.hasTaxonomy = false; + if (result.alignments == null) { + empty++; + } + total++; + const grouped = {}; + for (let j in result.alignments) { + for (let k in result.alignments[j]) { + let item = result.alignments[j][k]; + let split = item.target.split(" "); + item.target = split[0]; + item.description = split.slice(1).join(" "); + if (item.description.length > 1) { + result.hasDescription = true; + } + item.href = tryLinkTargetToDB(item.target, db); + item.target = tryFixTargetName(item.target, db); + item.id = "result-" + i + "-" + j; + item.active = false; + if (__APP__ != "foldseek" || data.mode != "tmalign") { + item.eval = + typeof item.eval === "string" + ? item.eval + : item.eval.toExponential(2); + } + if (__APP__ == "foldseek") { + item.prob = + typeof item.prob === "string" ? item.prob : item.prob.toFixed(2); + if (data.mode == "tmalign") { + item.eval = + typeof item.eval === "string" ? item.eval : item.eval.toFixed(3); + } else if (data.mode == "lolalign") { + item.eval = item.eval * 100; + item.eval = parseFloat(item.eval.toFixed(2)).toString(); + } + } + if ("taxId" in item) { + result.hasTaxonomy = true; } - total++; - const grouped = {}; - for (let j in result.alignments) { - for (let k in result.alignments[j]) { - let item = result.alignments[j][k]; - let split = item.target.split(' '); - item.target = split[0]; - item.description = split.slice(1).join(' '); - if (item.description.length > 1) { - result.hasDescription = true; - } - item.href = tryLinkTargetToDB(item.target, db); - item.target = tryFixTargetName(item.target, db); - item.id = 'result-' + i + '-' + j; - item.active = false; - if (__APP__ != "foldseek" || data.mode != "tmalign") { - item.eval = (typeof(item.eval) === "string") ? item.eval : item.eval.toExponential(2); - } - if (__APP__ == "foldseek") { - item.prob = (typeof(item.prob) === "string") ? item.prob : item.prob.toFixed(2); - if (data.mode == "tmalign") { - item.eval = (typeof(item.eval) === "string") ? item.eval : item.eval.toFixed(3); - } else if (data.mode == "lolalign") { - item.eval = item.eval * 100; - item.eval = parseFloat(item.eval.toFixed(2)).toString() - } - } - if ("taxId" in item) { - result.hasTaxonomy = true; - } - let groupId = item.complexid ?? k; - // console.log("Group ID: " + groupId + " complexid: " + item.complexid + " j: " + j) - if (!(grouped[groupId])) { - grouped[groupId] = []; - } - grouped[groupId].push(item); - } + let groupId = item.complexid ?? k; + // console.log("Group ID: " + groupId + " complexid: " + item.complexid + " j: " + j) + if (!grouped[groupId]) { + grouped[groupId] = []; } - result.alignments = grouped; + grouped[groupId].push(item); + } } - return (total != 0 && empty / total == 1) ? ({ results: [], mode : data.mode }) : data; + result.alignments = grouped; + } + return total != 0 && empty / total == 1 + ? { results: [], mode: data.mode } + : data; } export function splitAlphaNum(str) { - const len = str.length; - let i = 0; + const len = str.length; + let i = 0; - while (i < len) { - const cc = str.charCodeAt(i); - if (cc >= 48 && cc <= 57) { - break; - } - i++; + while (i < len) { + const cc = str.charCodeAt(i); + if (cc >= 48 && cc <= 57) { + break; } - const alpha = str.slice(0, i); + i++; + } + const alpha = str.slice(0, i); - let j = i; - while (j < len) { - const cc = str.charCodeAt(j); - if (cc < 48 || cc > 57) { - break; - } - j++; + let j = i; + while (j < len) { + const cc = str.charCodeAt(j); + if (cc < 48 || cc > 57) { + break; } - const numeric = str.slice(i, j); + j++; + } + const numeric = str.slice(i, j); - let substitution = ""; - if (j < len && str.charCodeAt(j) === 58 /* ':' */) { - substitution = str.slice(j); - } + let substitution = ""; + if (j < len && str.charCodeAt(j) === 58 /* ':' */) { + substitution = str.slice(j); + } - return [alpha, numeric, substitution]; + return [alpha, numeric, substitution]; } function computeInterresidueDist(splitTarget) { - const dist = []; - let lastChain = null; - let lastPos = null; + const dist = []; + let lastChain = null; + let lastPos = null; - for (let i = 1; i < splitTarget.length; ++i) { - const curr = splitTarget[i]; + for (let i = 1; i < splitTarget.length; ++i) { + const curr = splitTarget[i]; - if (curr === '_') { - dist.push(null); - continue; - } + if (curr === "_") { + dist.push(null); + continue; + } - let [currChain, currPos] = splitAlphaNum(curr); - currPos = currPos | 0; + let [currChain, currPos] = splitAlphaNum(curr); + currPos = currPos | 0; - // Find last valid residue before current - let j = i - 1; - while (j >= 0 && splitTarget[j] === '_') { - j--; - } - if (j < 0) { - dist.push(null); - lastChain = currChain; - lastPos = currPos; - continue; - } + // Find last valid residue before current + let j = i - 1; + while (j >= 0 && splitTarget[j] === "_") { + j--; + } + if (j < 0) { + dist.push(null); + lastChain = currChain; + lastPos = currPos; + continue; + } - let [prevChain, prevPos] = splitAlphaNum(splitTarget[j]); - prevPos = prevPos | 0; + let [prevChain, prevPos] = splitAlphaNum(splitTarget[j]); + prevPos = prevPos | 0; - const delta = (currChain === prevChain) ? (currPos - prevPos) : currPos; - dist.push(delta); + const delta = currChain === prevChain ? currPos - prevPos : currPos; + dist.push(delta); - // Update last valid residue - lastChain = currChain; - lastPos = currPos; - } + // Update last valid residue + lastChain = currChain; + lastPos = currPos; + } - return dist; + return dist; } export function parseResultsFoldDisco(data) { - let empty = 0; - let total = 0; - for (let i in data.results) { - let result = data.results[i]; - let db = result.db; - result.hasDescription = false; - result.hasTaxonomy = false; - if (result.alignments == null) { - empty++; + let empty = 0; + let total = 0; + for (let i in data.results) { + let result = data.results[i]; + let db = result.db; + result.hasDescription = false; + result.hasTaxonomy = false; + if (result.alignments == null) { + empty++; + } + total++; + result.queryresidues = {}; + const grouped = {}; + let meta = null; + if ("meta" in result) { + meta = {}; + for (let j in result.meta) { + let entry = result.meta[j]; + meta[entry.key] = entry; + } + } + result.meta = null; + for (let j in result.alignments) { + let item = result.alignments[j]; + let split = item.target.split("/"); + item.target = split[split.length - 1]; + + const splitTarget = item.targetresidues.split(","); + item.gaps = splitTarget.reduce((acc, s) => { + return acc + (s == "_" ? "0" : "1"); + }, ""); + item.interresiduedist = computeInterresidueDist(splitTarget); + item.idfscore = item.idfscore.toFixed(3); + item.rmsd = item.rmsd.toFixed(3); + + item.queryresidues.split(",").map((r) => { + let [chain, pos, _] = splitAlphaNum(r); + if (!(chain in result.queryresidues)) { + result.queryresidues[chain] = new Set(); } - total++; - result.queryresidues = {}; - const grouped = {}; - let meta = null; - if ("meta" in result) { - meta = {}; - for (let j in result.meta) { - let entry = result.meta[j]; - meta[entry.key] = entry; - } + result.queryresidues[chain].add(pos - 0); + }); + + item.href = tryLinkTargetToDB(item.target, db); + item.targetname = tryFixTargetName(item.target, db).toUpperCase(); + item.id = "result-" + i + "-" + j; + if (meta != null) { + let header = meta[item.dbkey].header; + let split = header.split(" "); + item.description = split.slice(1).join(" "); + if (item.description.length > 1) { + result.hasDescription = true; } - result.meta = null; - for (let j in result.alignments) { - let item = result.alignments[j]; - let split = item.target.split('/'); - item.target = split[split.length-1]; - - const splitTarget = item.targetresidues.split(','); - item.gaps = splitTarget - .reduce((acc, s) => { - return acc + ((s == '_') ? '0' : '1'); - }, ''); - item.interresiduedist = computeInterresidueDist(splitTarget); - item.idfscore = item.idfscore.toFixed(3); - item.rmsd = item.rmsd.toFixed(3); - - item.queryresidues - .split(',') - .map(r => { - let [chain, pos, _] = splitAlphaNum(r); - if (!(chain in result.queryresidues)) { - result.queryresidues[chain] = new Set(); - } - result.queryresidues[chain].add(pos - 0); - }); - - item.href = tryLinkTargetToDB(item.target, db); - item.targetname = tryFixTargetName(item.target, db).toUpperCase(); - item.id = 'result-' + i + '-' + j; - if (meta != null) { - let header = meta[item.dbkey].header; - let split = header.split(' '); - item.description = split.slice(1).join(' '); - if (item.description.length > 1) { - result.hasDescription = true; - } - if ("taxId" in meta[item.dbkey]) { - item.taxId = meta[item.dbkey].taxId; - item.taxName = meta[item.dbkey].taxName; - result.hasTaxonomy = true; - } - } - let groupId = j; - if (!(grouped[groupId])) { - grouped[groupId] = []; - } - grouped[j].push(item); + if ("taxId" in meta[item.dbkey]) { + item.taxId = meta[item.dbkey].taxId; + item.taxName = meta[item.dbkey].taxName; + result.hasTaxonomy = true; } - result.alignments = grouped; - Object.keys(result.queryresidues).forEach(function(key, _) { - result.queryresidues[key] = Array.from(result.queryresidues[key]); - }); + } + let groupId = j; + if (!grouped[groupId]) { + grouped[groupId] = []; + } + grouped[j].push(item); } - return (total != 0 && empty / total == 1) ? ({ results: [], mode : data.mode }) : data; + result.alignments = grouped; + Object.keys(result.queryresidues).forEach(function (key, _) { + result.queryresidues[key] = Array.from(result.queryresidues[key]); + }); + } + return total != 0 && empty / total == 1 + ? { results: [], mode: data.mode } + : data; } export function dateTime() { - // Generates current YYYY_MM_DD_HH_MM_SS timestamp - return new Date().toLocaleString('sv').replace(' ', '_').replaceAll('-', '_').replaceAll(':', '_'); + // Generates current YYYY_MM_DD_HH_MM_SS timestamp + return new Date() + .toLocaleString("sv") + .replace(" ", "_") + .replaceAll("-", "_") + .replaceAll(":", "_"); } export function djb2(str) { @@ -319,94 +398,136 @@ export function djb2(str) { } export function downloadBlob(blob, name) { - const link = document.createElement('a'); - // const date = new Date().toLocaleString('sv').replace(' ', '_').replaceAll('-', '_').replaceAll(':', '_'); - link.href = URL.createObjectURL(blob); - link.download = name; - // link.download = `${this.$STRINGS.APP_NAME}_${date}.json`; - link.click(); - URL.revokeObjectURL(link.href); + const link = document.createElement("a"); + // const date = new Date().toLocaleString('sv').replace(' ', '_').replaceAll('-', '_').replaceAll(':', '_'); + link.href = URL.createObjectURL(blob); + link.download = name; + // link.download = `${this.$STRINGS.APP_NAME}_${date}.json`; + link.click(); + URL.revokeObjectURL(link.href); } export function download(data, name) { - const json = JSON.stringify(data); - const blob = new Blob([json], { type: 'application/json' }); - const link = document.createElement('a'); - // const date = new Date().toLocaleString('sv').replace(' ', '_').replaceAll('-', '_').replaceAll(':', '_'); - link.href = URL.createObjectURL(blob); - link.download = name; - // link.download = `${this.$STRINGS.APP_NAME}_${date}.json`; - link.click(); - URL.revokeObjectURL(link.href); + const json = JSON.stringify(data); + const blob = new Blob([json], { type: "application/json" }); + const link = document.createElement("a"); + // const date = new Date().toLocaleString('sv').replace(' ', '_').replaceAll('-', '_').replaceAll(':', '_'); + link.href = URL.createObjectURL(blob); + link.download = name; + // link.download = `${this.$STRINGS.APP_NAME}_${date}.json`; + link.click(); + URL.revokeObjectURL(link.href); } export function parseResultsList(data) { - let hits = []; - for (let result of data) { - hits.push(parseResults(result)); - } - return hits; + let hits = []; + for (let result of data) { + hits.push(parseResults(result)); + } + return hits; } // Map 0-based indices in the alignment to corresponding 1-based indices in the structure export function makePositionMap(realStart, alnString) { - let map = Array(alnString.length); - for (let i = 0, gaps = 0; i < alnString.length; i++) { - if (alnString[i] === '-') { - map[i] = null; - gaps++; - } else { - map[i] = realStart + i - gaps; - } + let map = Array(alnString.length); + for (let i = 0, gaps = 0; i < alnString.length; i++) { + if (alnString[i] === "-") { + map[i] = null; + gaps++; + } else { + map[i] = realStart + i - gaps; } - return map + } + return map; } export const oneToThree = { - "A":"ALA", "R":"ARG", "N":"ASN", "D":"ASP", - "C":"CYS", "E":"GLU", "Q":"GLN", "G":"GLY", - "H":"HIS", "I":"ILE", "L":"LEU", "K":"LYS", - "M":"MET", "F":"PHE", "P":"PRO", "S":"SER", - "T":"THR", "W":"TRP", "Y":"TYR", "V":"VAL", - "U":"SEC", "O":"PHL", "X":"XAA" - }; + A: "ALA", + R: "ARG", + N: "ASN", + D: "ASP", + C: "CYS", + E: "GLU", + Q: "GLN", + G: "GLY", + H: "HIS", + I: "ILE", + L: "LEU", + K: "LYS", + M: "MET", + F: "PHE", + P: "PRO", + S: "SER", + T: "THR", + W: "TRP", + Y: "TYR", + V: "VAL", + U: "SEC", + O: "PHL", + X: "XAA", +}; export const threeToOne = { - "ALA":"A", "ARG":"R", "ASN":"N", "ASP":"D", - "CYS":"C", "GLU":"E", "GLN":"Q", "GLY":"G", - "HIS":"H", "ILE":"I", "LEU":"L", "LYS":"K", - "MET":"M", "PHE":"F", "PRO":"P", "SER":"S", - "THR":"T", "TRP":"W", "TYR":"Y", "VAL":"V", - "SEC":"U", "PHL":"O", "XAA":"X" + ALA: "A", + ARG: "R", + ASN: "N", + ASP: "D", + CYS: "C", + GLU: "E", + GLN: "Q", + GLY: "G", + HIS: "H", + ILE: "I", + LEU: "L", + LYS: "K", + MET: "M", + PHE: "F", + PRO: "P", + SER: "S", + THR: "T", + TRP: "W", + TYR: "Y", + VAL: "V", + SEC: "U", + PHL: "O", + XAA: "X", }; export function xyz(structure, resIndex) { - var rp = structure.getResidueProxy() - var ap = structure.getAtomProxy() - rp.index = resIndex - ap.index = rp.getAtomIndexByName('CA') - return [ap.x, ap.y, ap.z] + var rp = structure.getResidueProxy(); + var ap = structure.getAtomProxy(); + rp.index = resIndex; + ap.index = rp.getAtomIndexByName("CA"); + return [ap.x, ap.y, ap.z]; } function atomToPDBRow(ap) { - const { serial, atomname, resname, chainname, resno, inscode, x, y, z } = ap - return `ATOM ${serial.toString().padStart(5)}${atomname.padStart(4)} ${resname.padStart(3)} ${chainname.padStart(1)}${resno.toString().padStart(4)} ${inscode.padStart(1)} ${x.toFixed(3).padStart(8)}${y.toFixed(3).padStart(8)}${z.toFixed(3).padStart(8)}` + const { serial, atomname, resname, chainname, resno, inscode, x, y, z } = ap; + return `ATOM ${serial.toString().padStart(5)}${atomname.padStart( + 4 + )} ${resname.padStart(3)} ${chainname.padStart(1)}${resno + .toString() + .padStart(4)} ${inscode.padStart(1)} ${x.toFixed(3).padStart(8)}${y + .toFixed(3) + .padStart(8)}${z.toFixed(3).padStart(8)}`; } export function makeChainMap(structure, sele) { - let map = new Map() - let idx = 1; - structure.eachResidue(rp => { - map.set(idx++, { index: rp.index, resno: rp.resno }); - }, new Selection(sele)); - return map + let map = new Map(); + let idx = 1; + structure.eachResidue((rp) => { + map.set(idx++, { index: rp.index, resno: rp.resno }); + }, new Selection(sele)); + return map; } export function makeSubPDB(structure, sele) { - let pdb = []; - structure.eachAtom(ap => { pdb.push(atomToPDBRow(ap)) }, new Selection(sele)); - return pdb.join('\n') -} + let pdb = []; + structure.eachAtom((ap) => { + pdb.push(atomToPDBRow(ap)); + }, new Selection(sele)); + return pdb.join("\n"); +} /** * Create a mock PDB from Ca data @@ -414,26 +535,85 @@ export function makeSubPDB(structure, sele) { * Will have to change if/when swapping to fuller data */ export function mockPDB(ca, seq, chain) { - const atoms = ca.split(',') - const pdb = new Array() - let j = 1 - for (let i = 0; i < atoms.length; i += 3, j++) { - let [x, y, z] = atoms.slice(i, i + 3).map(element => parseFloat(element)) - // if (x == 0 && y == 0 && z == 0) continue; - pdb.push( - 'ATOM ' - + j.toString().padStart(5) - + ' CA ' + oneToThree[seq != "" && (atoms.length/3) == seq.length ? seq[i/3] : 'A'] - + chain.toString().padStart(2) - + j.toString().padStart(4) - + ' ' - + x.toFixed(3).padStart(8) - + y.toFixed(3).padStart(8) - + z.toFixed(3).padStart(8) - + ' 1.00 0.00 C ' - ) + const atoms = ca.split(","); + const pdb = new Array(); + let j = 1; + for (let i = 0; i < atoms.length; i += 3, j++) { + let [x, y, z] = atoms.slice(i, i + 3).map((element) => parseFloat(element)); + // if (x == 0 && y == 0 && z == 0) continue; + pdb.push( + "ATOM " + + j.toString().padStart(5) + + " CA " + + oneToThree[ + seq != "" && atoms.length / 3 == seq.length ? seq[i / 3] : "A" + ] + + chain.toString().padStart(2) + + j.toString().padStart(4) + + " " + + x.toFixed(3).padStart(8) + + y.toFixed(3).padStart(8) + + z.toFixed(3).padStart(8) + + " 1.00 0.00 C " + ); + } + return pdb.join("\n"); +} + +export function mergePdbs(chainPdbs /* [{pdb, chain}] */) { + let serial = 1; + const out = []; + + for (const { pdb, chain } of chainPdbs) { + const lines = pdb.split(/\r?\n/); + for (const line of lines) { + if (/^(ATOM |HETATM)/.test(line)) { + // reassign atom serial no. + let s = serial.toString().padStart(5, " "); + let l = line.padEnd(80, " "); + l = l.slice(0, 6) + s + l.slice(11); + + // change chain_id + l = l.substring(0, 21) + (chain[0] || "A") + l.substring(22); + + out.push(l); + serial++; + } } - return pdb.join('\n') + out.push("TER"); + } + + out.push("END"); + return out.join("\n"); +} + +/** + * + * @param {*} chainPdbs : Ca only pdb files with chain information in [{pdb, chain}] format + * @returns concatenated pdb string + * @abstract Concatenate multiple chains into one pdb files with single chain A + */ +export function concatenatePdbs(chainPdbs /* [{pdb, chain}] */) { + let serial = 1; + const out = []; + + for (const { pdb, chain } of chainPdbs) { + const lines = pdb.split(/\r?\n/); + for (const line of lines) { + if (/^(ATOM |HETATM)/.test(line)) { + // reassign atom serial no. and residue sequence no. + let s = serial.toString().padStart(5, " "); + let rs = serial.toString().padStart(4, " "); + let l = line.padEnd(80, " "); + l = l.slice(0, 6) + s + l.slice(11, 21) + "A" + rs + l.slice(26); + out.push(l); + serial++; + } + } + } + out.push("TER"); + out.push("END"); + return out.join("\n"); } /* ------ The rotation matrix to rotate Chain_1 to Chain_2 ------ */ @@ -449,22 +629,22 @@ export function mockPDB(ca, seq, chain) { /* Z[i] = t[2] + u[2][0]*x[i] + u[2][1]*y[i] + u[2][2]*z[i]; */ /* } */ export function transformStructure(structure, t, u) { - structure.eachAtom(atom => { - const [x, y, z] = [atom.x, atom.y, atom.z]; - atom.x = t[0] + u[0][0] * x + u[0][1] * y + u[0][2] * z; - atom.y = t[1] + u[1][0] * x + u[1][1] * y + u[1][2] * z; - atom.z = t[2] + u[2][0] * x + u[2][1] * y + u[2][2] * z; - }) + structure.eachAtom((atom) => { + const [x, y, z] = [atom.x, atom.y, atom.z]; + atom.x = t[0] + u[0][0] * x + u[0][1] * y + u[0][2] * z; + atom.y = t[1] + u[1][0] * x + u[1][1] * y + u[1][2] * z; + atom.z = t[2] + u[2][0] * x + u[2][1] * y + u[2][2] * z; + }); } export function debounce(func, delay) { let timeoutId; - return function() { + return function () { const context = this; - const args = arguments; + const args = arguments; clearTimeout(timeoutId); - timeoutId = setTimeout(function() { - func.apply(context, args); + timeoutId = setTimeout(function () { + func.apply(context, args); }, delay); }; } @@ -472,134 +652,270 @@ export function debounce(func, delay) { // Generate THREE.Matrix4 from 3x3 rotation and 1x3 translation matrices // Can give this directly to StructureComponent.setTransform() to superpose export function makeMatrix4(translation, rotation) { - const u = rotation.slice(); - for (let i = 0; i < 3; i++) { - u[i].push(translation[i]); - } - const nglMatrix = new Matrix4(); - const flatMatrix = [].concat(...u, [0, 0, 0, 1]); - nglMatrix.set(...flatMatrix); - return nglMatrix; + const u = rotation.slice(); + for (let i = 0; i < 3; i++) { + u[i].push(translation[i]); + } + const nglMatrix = new Matrix4(); + const flatMatrix = [].concat(...u, [0, 0, 0, 1]); + nglMatrix.set(...flatMatrix); + return nglMatrix; } // Decompose Matrix4 into Quaternion, Position and Scale // Slerp between Quaternions, linear interpolate position for some t (0.0-1.0) // Compose new Matrix4 for transformation. export function interpolateMatrices(a, b, t) { - const quaternionA = new Quaternion(); - const positionA = new Vector3(); - const scaleA = new Vector3(); - const quaternionB = new Quaternion(); - const positionB = new Vector3(); - const scaleB = new Vector3(); - a.decompose(positionA, quaternionA, scaleA); - b.decompose(positionB, quaternionB, scaleB); - const quaternion = new Quaternion(); - quaternion.slerp(quaternionB, t); - const position = new Vector3(); - position.lerpVectors(positionA, positionB, t); - const matrix = new Matrix4(); - matrix.compose(position, quaternion, scaleA); - return matrix; + const quaternionA = new Quaternion(); + const positionA = new Vector3(); + const scaleA = new Vector3(); + const quaternionB = new Quaternion(); + const positionB = new Vector3(); + const scaleB = new Vector3(); + a.decompose(positionA, quaternionA, scaleA); + b.decompose(positionB, quaternionB, scaleB); + const quaternion = new Quaternion(); + quaternion.slerp(quaternionB, t); + const position = new Vector3(); + position.lerpVectors(positionA, positionB, t); + const matrix = new Matrix4(); + matrix.compose(position, quaternion, scaleA); + return matrix; } export function animateMatrix(structure, newMatrix, duration) { - let startTime = null; - const oldMatrix = structure.matrix; - const animate = (currentTime) => { - if (!startTime) { - startTime = currentTime; + let startTime = null; + const oldMatrix = structure.matrix; + const animate = (currentTime) => { + if (!startTime) { + startTime = currentTime; + } + let progress = Math.min(1, (currentTime - startTime) / duration); + let interpolated = interpolateMatrices(oldMatrix, newMatrix, progress); + structure.setTransform(interpolated); + if (progress < 1) { + window.requestAnimationFrame(animate); + } + }; + window.requestAnimationFrame(animate); +} + +export function checkMultimer(pdbString) { + const lines = pdbString.split("\n"); + const models = {}; + const chainSet = new Set(); + + const trimmed = pdbString.trimStart(); + const isCIF = trimmed[0] === "#" || trimmed.startsWith("data_"); + + if (!isCIF) { + let currentModel = null; + + lines.forEach((line) => { + const recordType = line.substring(0, 6).trim(); + + if (recordType === "MODEL") { + currentModel = line.substring(10, 14).trim(); + models[currentModel] = new Set(); + } else if (recordType === "ATOM") { + const chainId = line.substring(21, 22); + if (currentModel) { + models[currentModel].add(chainId); + } else { + chainSet.add(chainId); } - let progress = Math.min(1, (currentTime - startTime) / duration); - let interpolated = interpolateMatrices(oldMatrix, newMatrix, progress); - structure.setTransform(interpolated); - if (progress < 1) { - window.requestAnimationFrame(animate); + } else if (recordType === "ENDMDL") { + currentModel = null; + } + }); + } else { + let inLoop = false; + let modelIndex = -1; + let chainIndex = -1; + let atomIndex = -1; + let columnCount = 0; + + lines.forEach((line) => { + if (line.startsWith("loop_")) { + inLoop = true; + columnCount = 0; + } else if (inLoop && line.startsWith("_atom_site.")) { + const colName = line.trim(); + if (colName === "_atom_site.pdbx_PDB_model_num") { + modelIndex = columnCount; + } else if (colName === "_atom_site.auth_asym_id") { + chainIndex = columnCount; + } else if (colName === "_atom_site.group_PDB") { + atomIndex = columnCount; + } + columnCount++; + } else if (inLoop && line.startsWith("#")) { + inLoop = false; + } else if (inLoop && line.trim()) { + const dataColumns = line.trim().split(/\s+/); + const groupPDB = atomIndex !== -1 ? dataColumns[atomIndex] : null; + if (groupPDB !== "ATOM") { + return; } + + const modelNum = modelIndex !== -1 ? dataColumns[modelIndex] : null; + const chainId = chainIndex !== -1 ? dataColumns[chainIndex] : null; + + if (modelNum) { + if (!models[modelNum]) { + models[modelNum] = new Set(); + } + models[modelNum].add(chainId); + } else { + chainSet.add(chainId); + } + } + }); + } + + if (Object.keys(models).length === 0) { + models["single model"] = chainSet; + } + return Object.values(models).some((model) => model.size > 1); +} + +export function getPdbText(comp) { + let pw = new PdbWriter(comp.structure, { renumberSerial: false }); + return pw + .getData() + .split("\n") + .filter((line) => line.startsWith("ATOM")) + .join("\n"); +} + +export const humanReadibleFormat = (bytes) => { + const u = ["B", "KB", "MB", "GB"]; + let i = 0; + while (bytes >= 1024 && i < u.length - 1) { + bytes /= 1024; + i++; + } + return `${bytes.toFixed(2)} ${u[i]}`; +}; + +export const calculateStrSize = (v) => { + if (!v) return -1; + + if (typeof v == "string") { + return v.length * 2; + } else if (v instanceof Array) { + let byte = 0; + + for (let s of v) { + if (typeof s == "string") { + byte += s.length * 2; + } else if (s instanceof Object && !s.text && typeof s.text == "string") { + byte += s.text.length * 2; + } + } + + return byte; + } else { + return -1; + } +}; + +export function storeChains(pdb) { + const arr = []; + let c = ""; + for (let line of pdb.split("\n")) { + if (line.startsWith("ATOM")) { + c = line.charAt(21); + } else if (line.startsWith("TER")) { + arr.push(c); } - window.requestAnimationFrame(animate); + } + if (arr.length == 0) { + arr.push(c); + } + return arr; } -export function checkMultimer(pdbString) { - const lines = pdbString.split('\n'); - const models = {}; - const chainSet = new Set(); - - const trimmed = pdbString.trimStart(); - const isCIF = trimmed[0] === "#" || trimmed.startsWith("data_"); - - if (!isCIF) { - let currentModel = null; - - lines.forEach(line => { - const recordType = line.substring(0, 6).trim(); - - if (recordType === 'MODEL') { - currentModel = line.substring(10, 14).trim(); - models[currentModel] = new Set(); - } else if (recordType === 'ATOM') { - const chainId = line.substring(21, 22); - if (currentModel) { - models[currentModel].add(chainId); - } else { - chainSet.add(chainId); - } - } else if (recordType === 'ENDMDL') { - currentModel = null; - } - }); - } else { - let inLoop = false; - let modelIndex = -1; - let chainIndex = -1; - let atomIndex = -1; - let columnCount = 0; - - lines.forEach(line => { - if (line.startsWith("loop_")) { - inLoop = true; - columnCount = 0; - } else if (inLoop && line.startsWith("_atom_site.")) { - const colName = line.trim(); - if (colName === "_atom_site.pdbx_PDB_model_num") { - modelIndex = columnCount; - } else if (colName === "_atom_site.auth_asym_id") { - chainIndex = columnCount; - } else if (colName === "_atom_site.group_PDB") { - atomIndex = columnCount; - } - columnCount++; - } else if (inLoop && line.startsWith("#")) { - inLoop = false; - } else if (inLoop && line.trim()) { - const dataColumns = line.trim().split(/\s+/); - const groupPDB = atomIndex !== -1 ? dataColumns[atomIndex] : null; - if (groupPDB !== "ATOM") { - return; - } - - const modelNum = modelIndex !== -1 ? dataColumns[modelIndex] : null; - const chainId = chainIndex !== -1 ? dataColumns[chainIndex] : null; - - if (modelNum) { - if (!models[modelNum]) { - models[modelNum] = new Set(); - } - models[modelNum].add(chainId); - } else { - chainSet.add(chainId); - } - } - }); +export function revertChainInfo(pdb, chains) { + if (chains.length == 0 || chains[0] == "") { + return pdb; + } + + const arr = []; + let i = 0; + + for (let line of pdb.split("\n")) { + if (line.startsWith("ATOM")) { + line = line.slice(0, 21) + chains[i] + line.slice(22); + } else if (line.startsWith("TER")) { + i++; } - if (Object.keys(models).length === 0) { - models['single model'] = chainSet; + arr.push(line); + } + + return arr.join("\n"); +} + +export function getAbsOffsetTop($el) { + var sum = 0; + while ($el) { + sum += $el.offsetTop; + $el = $el.offsetParent; + } + return sum; +} + +export const getChainName = (name) => { + if (/_v[0-9]+$/.test(name) || /^AF-\W+-/.test(name)) { + return "A"; + } + + let pos = name.lastIndexOf("_"); + if (pos != -1) { + let match = name.substring(pos + 1); + return match.length >= 1 && isNaN(Number(match[0])) ? match[0] : "A"; + } + // fallback + return "A"; +}; + +export const getAccession = (name) => { + if (/^AF-\w+-/.test(name)) { + name = name.split("-")[1]; + } + + if (/_v[0-9]+$/.test(name)) { + return name; + } + + if (/_unrelaxed_rank_/.test(name)) { + let pos = name.indexOf("_unrelaxed_rank_"); + return pos != -1 ? name.substring(0, pos) : name; + } + + let pos = name.lastIndexOf("_"); + return pos != -1 ? name.substring(0, pos) : name; +}; + +/** + * Throttle function + * @param {Function} func - function to be applied throttle + * @param {number} delay - delay in ms unit + * @returns {Function} - new function with throttle + */ +export function throttle(func, delay) { + let lastCallTime = 0; + + return function (...args) { + const context = this; + const now = Date.now(); + + if (now - lastCallTime >= delay) { + lastCallTime = now; + func.apply(context, args); } - return Object.values(models).some(model => model.size > 1); + }; } -export function getPdbText(comp) { - let pw = new PdbWriter(comp.structure, { renumberSerial: false }); - return pw.getData().split('\n').filter(line => line.startsWith('ATOM')).join('\n'); -} \ No newline at end of file +export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/frontend/lib/HistoryMixin.js b/frontend/lib/HistoryMixin.js index 85e4ba9..78397b8 100644 --- a/frontend/lib/HistoryMixin.js +++ b/frontend/lib/HistoryMixin.js @@ -1,75 +1,75 @@ const fakeLocalStorage = (() => { - let store = {}; - return { - getItem(key) { - return store[key] || null; - }, - setItem(key, value) { - store[key] = value.toString(); - }, - removeItem(key) { - delete store[key]; - }, - clear() { - store = {}; - } - }; + let store = {}; + return { + getItem(key) { + return store[key] || null; + }, + setItem(key, value) { + store[key] = value.toString(); + }, + removeItem(key) { + delete store[key]; + }, + clear() { + store = {}; + }, + }; })(); let localStorageEnabled = false; try { - if (typeof window.localStorage !== 'undefined') { - localStorageEnabled = true; - } + if (typeof window.localStorage !== "undefined") { + localStorageEnabled = true; + } } catch (e) { - localStorageEnabled = false; + localStorageEnabled = false; } const storage = localStorageEnabled ? window.localStorage : fakeLocalStorage; const StorageWrapper = (prefix) => { - return { - getItem(key) { - return storage.getItem(`${prefix}.${key}`); - }, - setItem(key, value) { - storage.setItem(`${prefix}.${key}`, value); - }, - removeItem(key) { - storage.removeItem(`${prefix}.${key}`); - }, - baseStorage: storage, - clear() { - let keys = Object.keys(storage); - for (let key of keys) { - if (key.startsWith(prefix)) { - storage.removeItem(key); - } - } + return { + getItem(key) { + return storage.getItem(`${prefix}.${key}`); + }, + setItem(key, value) { + storage.setItem(`${prefix}.${key}`, value); + }, + removeItem(key) { + storage.removeItem(`${prefix}.${key}`); + }, + baseStorage: storage, + clear() { + let keys = Object.keys(storage); + for (let key of keys) { + if (key.startsWith(prefix)) { + storage.removeItem(key); } - } + } + }, + }; }; // Mixin for history-related methods const HistoryMixin = { - methods: { - addToHistory(uuid) { - if (!uuid) { - return; - } + methods: { + addToHistory(uuid) { + if (!uuid) { + return; + } - let history = JSON.parse(storage.getItem('history') || '[]'); + let history = JSON.parse(storage.getItem("history") || "[]"); - let foundIndex = history.findIndex(item => item.id === uuid); - if (foundIndex === -1) { - history.unshift({ time: +new Date(), id: uuid }); - } else { - history[foundIndex].time = +new Date(); - } + let foundIndex = history.findIndex((item) => item.id === uuid); + if (foundIndex === -1) { + history.unshift({ time: +new Date(), id: uuid }); + } else { + history[foundIndex].time = +new Date(); + } - storage.setItem('history', JSON.stringify(history)); - } - } + storage.setItem("history", JSON.stringify(history)); + }, + }, }; export { localStorageEnabled, storage, StorageWrapper, HistoryMixin }; diff --git a/frontend/lib/throttle.js b/frontend/lib/throttle.js new file mode 100644 index 0000000..3dcba37 --- /dev/null +++ b/frontend/lib/throttle.js @@ -0,0 +1,19 @@ +/** + * Throttle function + * @param {Function} func - function to be applied throttle + * @param {number} delay - delay in ms unit + * @returns {Function} - new function with throttle + */ +export function throttle(func, delay) { + let lastCallTime = 0; + + return function (...args) { + const context = this; + const now = Date.now(); + + if (now - lastCallTime >= delay) { + lastCallTime = now; + func.apply(context, args); + } + }; +} diff --git a/frontend/navigationButton.vue b/frontend/navigationButton.vue new file mode 100644 index 0000000..8452fc9 --- /dev/null +++ b/frontend/navigationButton.vue @@ -0,0 +1,165 @@ + + + \ No newline at end of file