DefaultTranscriptsList.tsx 5.25 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

17
import React, { useEffect } from 'react';
18
import { useSelector, useDispatch } from 'react-redux';
19
import { Pick2, Pick3 } from 'ts-multipick';
20

21
import { getFeatureLength } from 'src/content/app/entity-viewer/shared/helpers/entity-helpers';
22
import { getTranscriptSortingFunction } from 'src/content/app/entity-viewer/shared/helpers/transcripts-sorter';
23

24
import { filterTranscripts } from 'src/content/app/entity-viewer/shared/helpers/transcripts-filter';
25

26 27
import {
  getExpandedTranscriptIds,
Ridwan Amode's avatar
Ridwan Amode committed
28
  getExpandedTranscriptDownloadIds,
29
  getExpandedTranscriptMoreInfoIds,
30
  getFilters,
Ridwan Amode's avatar
Ridwan Amode committed
31
  getSortingRule
32
} from 'src/content/app/entity-viewer/state/gene-view/transcripts/geneViewTranscriptsSelectors';
33
import { toggleTranscriptInfo } from 'src/content/app/entity-viewer/state/gene-view/transcripts/geneViewTranscriptsSlice';
34

35 36 37
import DefaultTranscriptsListItem, {
  DefaultTranscriptListItemProps
} from './default-transcripts-list-item/DefaultTranscriptListItem';
38 39

import { TicksAndScale } from 'src/content/app/entity-viewer/gene-view/components/base-pairs-ruler/BasePairsRuler';
40 41 42 43 44 45
import { FullGene } from 'src/shared/types/thoas/gene';
import { FullTranscript } from 'src/shared/types/thoas/transcript';
import { FullProductGeneratingContext } from 'src/shared/types/thoas/productGeneratingContext';
import { FullCDS } from 'src/shared/types/thoas/cds';
import { SplicedExon } from 'src/shared/types/thoas/exon';
import { Slice } from 'src/shared/types/thoas/slice';
46 47 48

import styles from './DefaultTranscriptsList.scss';

49 50 51 52 53 54 55 56 57
type ProductGeneratingContext = {
  product_type: FullProductGeneratingContext['product_type'];
  cds: Pick<FullCDS, 'relative_start' | 'relative_end'>;
};
type Transcript = DefaultTranscriptListItemProps['transcript'] & {
  product_generating_contexts: ProductGeneratingContext[];
} & {
  spliced_exons: Array<Pick3<SplicedExon, 'exon', 'slice', 'location'>>;
} & Pick2<FullTranscript, 'slice', 'location'>;
58

59 60 61
type Gene = DefaultTranscriptListItemProps['gene'] & {
  stable_id: FullGene['stable_id'];
  transcripts: Array<Transcript>;
62
  slice: Pick2<Slice, 'location', 'length'>;
63 64 65
};

export type Props = {
66 67 68 69 70
  gene: Gene;
  rulerTicks: TicksAndScale;
};

const DefaultTranscriptslist = (props: Props) => {
71 72 73 74
  const expandedTranscriptIds = useSelector(getExpandedTranscriptIds);
  const expandedTranscriptDownloadIds = useSelector(
    getExpandedTranscriptDownloadIds
  );
75 76 77
  const expandedTranscriptMoreInfoIds = useSelector(
    getExpandedTranscriptMoreInfoIds
  );
78 79 80 81 82
  const sortingRule = useSelector(getSortingRule);
  const filters = useSelector(getFilters);
  const dispatch = useDispatch();

  const { gene } = props;
Ridwan Amode's avatar
Ridwan Amode committed
83

84 85 86 87
  const filteredTranscripts = filterTranscripts(gene.transcripts, filters);

  const sortingFunction = getTranscriptSortingFunction<Transcript>(sortingRule);
  const sortedTranscripts = sortingFunction(filteredTranscripts);
88

89
  useEffect(() => {
90
    const hasExpandedTranscripts = !!expandedTranscriptIds.length;
91 92 93

    // Expand the first transcript by default
    if (!hasExpandedTranscripts) {
94
      dispatch(toggleTranscriptInfo(sortedTranscripts[0].stable_id));
95 96 97
    }
  }, []);

98
  return (
99 100 101 102 103 104 105
    <div>
      <div className={styles.header}>
        <div className={styles.row}>
          <div className={styles.right}>Transcript ID</div>
        </div>
      </div>
      <div className={styles.content}>
106
        <StripedBackground {...props} />
107
        {sortedTranscripts.map((transcript, index) => {
108
          const expandTranscript = expandedTranscriptIds.includes(
109
            transcript.stable_id
110
          );
111
          const expandDownload = expandedTranscriptDownloadIds.includes(
112
            transcript.stable_id
113
          );
114 115 116
          const expandMoreInfo = expandedTranscriptMoreInfoIds.includes(
            transcript.stable_id
          );
117 118 119 120 121 122 123 124 125

          return (
            <DefaultTranscriptsListItem
              key={index}
              gene={gene}
              transcript={transcript}
              rulerTicks={props.rulerTicks}
              expandTranscript={expandTranscript}
              expandDownload={expandDownload}
126
              expandMoreInfo={expandMoreInfo}
127 128 129
            />
          );
        })}
130
      </div>
131 132 133 134 135 136
    </div>
  );
};

const StripedBackground = (props: Props) => {
  const { scale, ticks } = props.rulerTicks;
137
  const geneLength = getFeatureLength(props.gene);
138 139 140
  const extendedTicks = [1, ...ticks, geneLength];

  const stripes = extendedTicks.map((tick) => {
Andrey Azov's avatar
Andrey Azov committed
141
    const x = Math.floor(scale(tick) as number);
142 143 144 145 146 147 148
    const style = { left: `${x}px` };
    return <span key={x} className={styles.stripe} style={style} />;
  });

  return <div className={styles.stripedBackground}>{stripes}</div>;
};

149
export default DefaultTranscriptslist;