jbrowse-plugin-msaview 2.10.0 → 2.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/LaunchMsaView/components/BlastQuery/useCachedBlastResults.d.ts +1 -1
  2. package/dist/LaunchMsaView/components/BlastQuery/useCachedBlastResults.js +14 -17
  3. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +1 -1
  4. package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.d.ts +2 -1
  5. package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.js +48 -50
  6. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +4 -5
  7. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +6 -4
  8. package/dist/LaunchMsaView/components/useFeatureSequence.js +20 -11
  9. package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +1 -1
  10. package/dist/LaunchMsaView/extendStateModel.test.d.ts +1 -0
  11. package/dist/LaunchMsaView/extendStateModel.test.js +69 -0
  12. package/dist/LaunchMsaView/index.d.ts +2 -0
  13. package/dist/LaunchMsaView/index.js +44 -54
  14. package/dist/LaunchMsaView/launchTarget.d.ts +25 -0
  15. package/dist/LaunchMsaView/launchTarget.js +42 -0
  16. package/dist/LaunchMsaView/launchTarget.test.d.ts +1 -0
  17. package/dist/LaunchMsaView/launchTarget.test.js +54 -0
  18. package/dist/MsaViewPanel/doLaunchOrthologs.js +4 -2
  19. package/dist/MsaViewPanel/model.d.ts +4 -2
  20. package/dist/jbrowse-plugin-msaview.umd.production.min.js +29 -40
  21. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  22. package/dist/utils/ebiJobDispatcher.test.js +10 -1
  23. package/dist/utils/ncbiOrthologs.d.ts +1 -1
  24. package/dist/utils/ncbiOrthologs.js +1 -1
  25. package/dist/utils/ncbiTaxonomy.d.ts +19 -0
  26. package/dist/utils/ncbiTaxonomy.js +42 -9
  27. package/dist/utils/ncbiTaxonomy.test.d.ts +1 -0
  28. package/dist/utils/ncbiTaxonomy.test.js +69 -0
  29. package/dist/utils/useFetch.d.ts +22 -0
  30. package/dist/utils/useFetch.js +79 -0
  31. package/dist/utils/useFetch.test.d.ts +1 -0
  32. package/dist/utils/useFetch.test.js +23 -0
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/package.json +7 -6
  36. package/src/LaunchMsaView/components/BlastQuery/useCachedBlastResults.ts +16 -29
  37. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +1 -0
  38. package/src/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.tsx +63 -49
  39. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +3 -6
  40. package/src/LaunchMsaView/components/useFeatureSequence.ts +35 -15
  41. package/src/LaunchMsaView/extendStateModel.test.ts +76 -0
  42. package/src/LaunchMsaView/index.ts +47 -80
  43. package/src/LaunchMsaView/launchTarget.test.ts +77 -0
  44. package/src/LaunchMsaView/launchTarget.ts +65 -0
  45. package/src/MsaViewPanel/doLaunchOrthologs.ts +4 -2
  46. package/src/MsaViewPanel/model.ts +4 -2
  47. package/src/utils/ebiJobDispatcher.test.ts +10 -1
  48. package/src/utils/ncbiOrthologs.ts +1 -1
  49. package/src/utils/ncbiTaxonomy.test.ts +84 -0
  50. package/src/utils/ncbiTaxonomy.ts +52 -9
  51. package/src/utils/useFetch.test.ts +30 -0
  52. package/src/utils/useFetch.ts +116 -0
  53. package/src/version.ts +1 -1
  54. package/dist/LaunchMsaView/components/useSWRFeatureSequence.d.ts +0 -12
  55. package/dist/LaunchMsaView/components/useSWRFeatureSequence.js +0 -25
  56. package/dist/utils/swrConfig.d.ts +0 -8
  57. package/dist/utils/swrConfig.js +0 -8
  58. package/src/LaunchMsaView/components/useSWRFeatureSequence.ts +0 -54
  59. package/src/utils/swrConfig.ts +0 -8
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from 'vitest'
2
+
3
+ import { serializeKey } from './useFetch'
4
+
5
+ describe('serializeKey', () => {
6
+ test('a key identifies the fetch', () => {
7
+ expect(serializeKey('msa-list')).toBe(serializeKey('msa-list'))
8
+ expect(serializeKey(['NM_1', 'hg38'])).not.toBe(
9
+ serializeKey(['NM_1', 'mm39']),
10
+ )
11
+ })
12
+
13
+ test('no key means do not fetch', () => {
14
+ expect(serializeKey(null)).toBeNull()
15
+ expect(serializeKey(undefined)).toBeNull()
16
+ expect(serializeKey(false)).toBeNull()
17
+ expect(serializeKey('')).not.toBeNull()
18
+ })
19
+
20
+ test('an array key with a piece still missing means do not fetch', () => {
21
+ expect(serializeKey(['NM_1', undefined, 'feature-sequence'])).toBeNull()
22
+ })
23
+
24
+ // the trap this rule sets: a boolean IN a key reads as a missing piece, so a
25
+ // `!!selected` where a string belonged disables the fetch and raises nothing
26
+ test('a boolean in a key disables the fetch', () => {
27
+ expect(serializeKey(['gene', false])).toBeNull()
28
+ expect(serializeKey(['gene', true])).not.toBeNull()
29
+ })
30
+ })
@@ -0,0 +1,116 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+
3
+ // A trimmed copy of jbrowse-components' `packages/core/src/util/useFetch.ts`,
4
+ // vendored for the same reason `useLocalStorage` and `defaultCodonTable` are:
5
+ // it does not exist in the @jbrowse/core this plugin builds against, and a
6
+ // host-resolved import is exactly how published bundles have broken here
7
+ // before. Our own copy takes this out of the whack-a-mole.
8
+ //
9
+ // Trimmed because this plugin has no RPC: the core version also carries stop
10
+ // tokens, a throttled progress stream and a cross-component mutate registry,
11
+ // and nothing here has ever wanted any of the three. What is left is the
12
+ // {data, error, isLoading} the four call sites this replaced actually used --
13
+ // they were on SWR, whose only feature we relied on was fetch-once-per-key with
14
+ // background revalidation off, so dropping it also drops a dependency from the
15
+ // UMD bundle.
16
+
17
+ export type FetchKey = string | readonly unknown[] | null | undefined | false
18
+
19
+ const isNil = (k: unknown) => k === null || k === undefined || k === false
20
+
21
+ // A null result means "don't fetch": no key, or an array key with a missing
22
+ // piece. Note what that makes of a boolean IN a key -- `false` reads as a
23
+ // missing piece and silently disables the fetch, so put a string there.
24
+ export function serializeKey(key: FetchKey): string | null {
25
+ return isNil(key) || (Array.isArray(key) && key.some(isNil))
26
+ ? null
27
+ : JSON.stringify(key)
28
+ }
29
+
30
+ interface Settled<Data> {
31
+ /** the key AND the attempt, so a mutate() refetch is a different entry */
32
+ attempt: string
33
+ /** the key alone, which is the question the data answers */
34
+ question: string
35
+ data?: Data
36
+ error?: unknown
37
+ }
38
+
39
+ /**
40
+ * Fetch once per key, keeping loading and error state with the data instead of
41
+ * spread across three `useState`s and a cancellation flag.
42
+ *
43
+ * Everything the caller reads is derived from the one settled result, which is
44
+ * what keeps the states consistent: while a key change is in flight there is no
45
+ * frame showing the previous key's data as though it were the new key's. A
46
+ * `mutate()` refetch under the same key does leave its data up, because it is
47
+ * the same question asked again and blanking it flashes an empty list.
48
+ */
49
+ export function useFetch<Data>(
50
+ key: FetchKey,
51
+ fetcher: () => Promise<Data>,
52
+ { onSuccess }: { onSuccess?: (data: Data) => void } = {},
53
+ ) {
54
+ const serialized = serializeKey(key)
55
+ const [nonce, setNonce] = useState(0)
56
+ const [settled, setSettled] = useState<Settled<Data>>()
57
+
58
+ const attempt = serialized === null ? null : `${nonce}\u0000${serialized}`
59
+ const current = settled?.attempt === attempt
60
+ const answersThisKey = settled?.question === serialized
61
+
62
+ // The fetcher and the success callback are inline closures at every call site,
63
+ // so depending on their identity would refetch every render. This effect is
64
+ // declared before the fetch effect and has no dependency list, so it has
65
+ // already run with the current render's values by the time that one does.
66
+ const latest = useRef({ fetcher, onSuccess })
67
+ useEffect(() => {
68
+ latest.current = { fetcher, onSuccess }
69
+ })
70
+
71
+ useEffect(() => {
72
+ if (attempt === null || serialized === null) {
73
+ return undefined
74
+ }
75
+ let alive = true
76
+ latest.current
77
+ .fetcher()
78
+ .then(data => {
79
+ if (alive) {
80
+ setSettled({ attempt, question: serialized, data })
81
+ latest.current.onSuccess?.(data)
82
+ }
83
+ })
84
+ .catch((error: unknown) => {
85
+ if (alive) {
86
+ setSettled({ attempt, question: serialized, error })
87
+ }
88
+ })
89
+ return () => {
90
+ alive = false
91
+ }
92
+ }, [attempt, serialized])
93
+
94
+ return {
95
+ data: answersThisKey ? settled.data : undefined,
96
+ error: current ? settled.error : undefined,
97
+ isLoading: attempt !== null && !current,
98
+ mutate: useCallback(() => {
99
+ setNonce(n => n + 1)
100
+ }, []),
101
+ }
102
+ }
103
+
104
+ /** `value`, but only after it has stopped changing for `ms`. */
105
+ export function useDebounced<T>(value: T, ms: number) {
106
+ const [debounced, setDebounced] = useState(value)
107
+ useEffect(() => {
108
+ const timer = setTimeout(() => {
109
+ setDebounced(value)
110
+ }, ms)
111
+ return () => {
112
+ clearTimeout(timer)
113
+ }
114
+ }, [value, ms])
115
+ return debounced
116
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '2.10.0'
1
+ export const version = '2.10.2'
@@ -1,12 +0,0 @@
1
- import type { Feature } from '@jbrowse/core/util';
2
- export declare function useSWRFeatureSequence({ view, feature, }: {
3
- view: {
4
- assemblyNames?: string[];
5
- } | undefined;
6
- feature?: Feature;
7
- }): {
8
- sequence: {
9
- seq: string;
10
- } | undefined;
11
- error: any;
12
- };
@@ -1,25 +0,0 @@
1
- import { getSession } from '@jbrowse/core/util';
2
- import useSWR from 'swr';
3
- import { fetchSeq } from './fetchSeq';
4
- import { staticSwrConfig } from '../../utils/swrConfig';
5
- async function featureSequenceFetcher({ feature, assemblyName, view, }) {
6
- const session = getSession(view);
7
- const { start, end, refName } = feature.toJSON();
8
- const seq = await fetchSeq({
9
- start,
10
- end,
11
- refName,
12
- assemblyName,
13
- session,
14
- });
15
- return { seq };
16
- }
17
- export function useSWRFeatureSequence({ view, feature, }) {
18
- const assemblyName = view?.assemblyNames?.[0];
19
- const args = feature && assemblyName ? { feature, assemblyName, view } : undefined;
20
- const { data, error } = useSWR(args ? [args.feature.id(), args.assemblyName, 'feature-sequence'] : null, () => featureSequenceFetcher(args), staticSwrConfig);
21
- return {
22
- sequence: data,
23
- error,
24
- };
25
- }
@@ -1,8 +0,0 @@
1
- export declare const staticSwrConfig: {
2
- revalidateOnFocus: boolean;
3
- revalidateOnReconnect: boolean;
4
- revalidateIfStale: boolean;
5
- refreshWhenHidden: boolean;
6
- refreshWhenOffline: boolean;
7
- shouldRetryOnError: boolean;
8
- };
@@ -1,8 +0,0 @@
1
- export const staticSwrConfig = {
2
- revalidateOnFocus: false,
3
- revalidateOnReconnect: false,
4
- revalidateIfStale: false,
5
- refreshWhenHidden: false,
6
- refreshWhenOffline: false,
7
- shouldRetryOnError: false,
8
- };
@@ -1,54 +0,0 @@
1
- import { getSession } from '@jbrowse/core/util'
2
- import useSWR from 'swr'
3
-
4
- import { fetchSeq } from './fetchSeq'
5
- import { staticSwrConfig } from '../../utils/swrConfig'
6
-
7
- import type { Feature } from '@jbrowse/core/util'
8
-
9
- async function featureSequenceFetcher({
10
- feature,
11
- assemblyName,
12
- view,
13
- }: {
14
- feature: Feature
15
- assemblyName: string
16
- view: { assemblyNames?: string[] }
17
- }) {
18
- const session = getSession(view)
19
- const { start, end, refName } = feature.toJSON() as {
20
- start: number
21
- end: number
22
- refName: string
23
- }
24
- const seq = await fetchSeq({
25
- start,
26
- end,
27
- refName,
28
- assemblyName,
29
- session,
30
- })
31
- return { seq }
32
- }
33
-
34
- export function useSWRFeatureSequence({
35
- view,
36
- feature,
37
- }: {
38
- view: { assemblyNames?: string[] } | undefined
39
- feature?: Feature
40
- }) {
41
- const assemblyName = view?.assemblyNames?.[0]
42
- const args =
43
- feature && assemblyName ? { feature, assemblyName, view } : undefined
44
- const { data, error } = useSWR(
45
- args ? [args.feature.id(), args.assemblyName, 'feature-sequence'] : null,
46
- () => featureSequenceFetcher(args!),
47
- staticSwrConfig,
48
- )
49
-
50
- return {
51
- sequence: data,
52
- error,
53
- }
54
- }
@@ -1,8 +0,0 @@
1
- export const staticSwrConfig = {
2
- revalidateOnFocus: false,
3
- revalidateOnReconnect: false,
4
- revalidateIfStale: false,
5
- refreshWhenHidden: false,
6
- refreshWhenOffline: false,
7
- shouldRetryOnError: false,
8
- }