@jbrowse/core 4.1.13 → 4.1.15
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.
- package/esm/BaseFeatureWidget/index.d.ts +1 -0
- package/esm/BaseFeatureWidget/util.d.ts +3 -0
- package/esm/TextSearch/TextSearchManager.d.ts +1 -0
- package/esm/TextSearch/TextSearchManager.js +3 -0
- package/esm/assemblyManager/index.d.ts +1 -0
- package/esm/ui/DOMPurifySanitizedHTML.d.ts +4 -0
- package/esm/ui/DOMPurifySanitizedHTML.js +16 -0
- package/esm/ui/DropDownMenu.js +1 -1
- package/esm/ui/MenuTypes.d.ts +1 -2
- package/esm/ui/SanitizedHTML.js +33 -18
- package/esm/util/IntervalTree.js +1 -7
- package/esm/util/convertCodingSequenceToPeptides.js +3 -2
- package/esm/util/index.d.ts +2 -2
- package/esm/util/index.js +21 -21
- package/esm/util/io/RemoteFileWithRangeCache.d.ts +2 -10
- package/esm/util/io/RemoteFileWithRangeCache.js +102 -54
- package/esm/util/types/index.d.ts +15 -13
- package/package.json +12 -12
|
@@ -6,6 +6,7 @@ export interface Feat {
|
|
|
6
6
|
type?: string;
|
|
7
7
|
name?: string;
|
|
8
8
|
id?: string;
|
|
9
|
+
phase?: number;
|
|
9
10
|
}
|
|
10
11
|
export interface ParentFeat extends Feat {
|
|
11
12
|
uniqueId: string;
|
|
@@ -33,6 +34,7 @@ export declare function revlist(list: Feat[], seqlen: number): {
|
|
|
33
34
|
type?: string;
|
|
34
35
|
name?: string;
|
|
35
36
|
id?: string;
|
|
37
|
+
phase?: number;
|
|
36
38
|
}[];
|
|
37
39
|
export declare function calculateUTRs(cds: Feat[], exons: Feat[]): {
|
|
38
40
|
type: string;
|
|
@@ -40,6 +42,7 @@ export declare function calculateUTRs(cds: Feat[], exons: Feat[]): {
|
|
|
40
42
|
end: number;
|
|
41
43
|
name?: string;
|
|
42
44
|
id?: string;
|
|
45
|
+
phase?: number;
|
|
43
46
|
}[];
|
|
44
47
|
export declare function calculateUTRs2(cds: Feat[], parentFeat: Feat): {
|
|
45
48
|
type: string;
|
|
@@ -12,6 +12,7 @@ export default class TextSearchManager {
|
|
|
12
12
|
pluginManager: PluginManager;
|
|
13
13
|
adapterCache: QuickLRU<string, BaseTextSearchAdapter>;
|
|
14
14
|
constructor(pluginManager: PluginManager);
|
|
15
|
+
clearCache(): void;
|
|
15
16
|
loadTextSearchAdapters(searchScope: SearchScope): Promise<BaseTextSearchAdapter[]>;
|
|
16
17
|
relevantAdapters(searchScope: SearchScope): ({
|
|
17
18
|
[x: string]: any;
|
|
@@ -9,6 +9,9 @@ export default class TextSearchManager {
|
|
|
9
9
|
constructor(pluginManager) {
|
|
10
10
|
this.pluginManager = pluginManager;
|
|
11
11
|
}
|
|
12
|
+
clearCache() {
|
|
13
|
+
this.adapterCache.clear();
|
|
14
|
+
}
|
|
12
15
|
loadTextSearchAdapters(searchScope) {
|
|
13
16
|
return Promise.all(this.relevantAdapters(searchScope).map(async (conf) => {
|
|
14
17
|
const adapterId = readConfObject(conf, 'textSearchAdapterId');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { default } from './assemblyManager.ts';
|
|
2
|
+
export type { AssemblyBaseOpts } from './assemblyManager.ts';
|
|
2
3
|
export { default as assemblyConfigSchemaFactory } from './assemblyConfigSchema.ts';
|
|
3
4
|
export type { BaseAssemblyConfigSchema } from './assemblyConfigSchema.ts';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
3
|
+
import dompurify from 'dompurify';
|
|
4
|
+
export default function DOMPurifySanitizedHTML({ value, className, }) {
|
|
5
|
+
const spanRef = useRef(null);
|
|
6
|
+
useLayoutEffect(() => {
|
|
7
|
+
const el = spanRef.current;
|
|
8
|
+
if (el) {
|
|
9
|
+
for (const a of el.querySelectorAll('a')) {
|
|
10
|
+
a.setAttribute('rel', 'noopener noreferrer');
|
|
11
|
+
a.setAttribute('target', '_blank');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}, [value]);
|
|
15
|
+
return (_jsx("span", { ref: spanRef, className: className, dangerouslySetInnerHTML: { __html: dompurify.sanitize(value) } }));
|
|
16
|
+
}
|
package/esm/ui/DropDownMenu.js
CHANGED
|
@@ -19,6 +19,6 @@ function ButtonComponent(props) {
|
|
|
19
19
|
return (_jsx(Button, { ...props, className: classes.buttonRoot, children: props.children }));
|
|
20
20
|
}
|
|
21
21
|
const DropDownMenu = observer(function DropDownMenu({ menuTitle, menuItems, }) {
|
|
22
|
-
return (_jsxs(CascadingMenuButton, { menuItems: menuItems, color: "inherit", ButtonComponent: ButtonComponent, children: [menuTitle, _jsx(ArrowDropDown, {})] }));
|
|
22
|
+
return (_jsxs(CascadingMenuButton, { menuItems: menuItems, color: "inherit", ButtonComponent: ButtonComponent, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, children: [menuTitle, _jsx(ArrowDropDown, {})] }));
|
|
23
23
|
});
|
|
24
24
|
export default DropDownMenu;
|
package/esm/ui/MenuTypes.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { SvgIconProps } from '@mui/material';
|
|
2
1
|
export interface MenuDivider {
|
|
3
2
|
priority?: number;
|
|
4
3
|
type: 'divider';
|
|
@@ -13,7 +12,7 @@ export interface BaseMenuItem {
|
|
|
13
12
|
label: React.ReactNode;
|
|
14
13
|
priority?: number;
|
|
15
14
|
subLabel?: string;
|
|
16
|
-
icon?: React.
|
|
15
|
+
icon?: React.ElementType;
|
|
17
16
|
disabled?: boolean;
|
|
18
17
|
helpText?: string;
|
|
19
18
|
}
|
package/esm/ui/SanitizedHTML.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import dompurify from 'dompurify';
|
|
2
|
+
import { Suspense, lazy, useLayoutEffect, useRef } from 'react';
|
|
4
3
|
import escapeHTML from 'escape-html';
|
|
5
4
|
import { linkify } from "../util/index.js";
|
|
6
5
|
const htmlTags = [
|
|
@@ -36,26 +35,42 @@ const htmlTags = [
|
|
|
36
35
|
'u',
|
|
37
36
|
'ul',
|
|
38
37
|
];
|
|
39
|
-
|
|
38
|
+
const DOMPurifySanitizedHTML = lazy(() => import("./DOMPurifySanitizedHTML.js"));
|
|
40
39
|
const full = new RegExp(htmlTags.map(tag => String.raw `<${tag}\b[^>]*>`).join('|'), 'i');
|
|
41
40
|
function isHTML(str) {
|
|
42
41
|
return full.test(str);
|
|
43
42
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
function needsSanitization(str) {
|
|
44
|
+
return str.includes('<') || str.includes('://');
|
|
45
|
+
}
|
|
46
|
+
function SetHTML({ value, className }) {
|
|
47
|
+
const spanRef = useRef(null);
|
|
48
|
+
useLayoutEffect(() => {
|
|
49
|
+
const el = spanRef.current;
|
|
50
|
+
if (el) {
|
|
51
|
+
try {
|
|
52
|
+
el.setHTML(value);
|
|
53
|
+
for (const a of el.querySelectorAll('a')) {
|
|
54
|
+
a.setAttribute('rel', 'noopener noreferrer');
|
|
55
|
+
a.setAttribute('target', '_blank');
|
|
54
56
|
}
|
|
55
|
-
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
console.error(e);
|
|
60
|
+
}
|
|
56
61
|
}
|
|
57
|
-
}, []);
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
}, [value]);
|
|
63
|
+
return _jsx("span", { ref: spanRef, className: className });
|
|
64
|
+
}
|
|
65
|
+
export default function SanitizedHTML({ html: pre, className, }) {
|
|
66
|
+
const str = `${pre}`;
|
|
67
|
+
if (!needsSanitization(str)) {
|
|
68
|
+
return _jsx("span", { className: className, children: str });
|
|
69
|
+
}
|
|
70
|
+
const html = linkify(str);
|
|
71
|
+
const value = isHTML(html) ? html : escapeHTML(html);
|
|
72
|
+
if (typeof Element !== 'undefined' && Element.prototype.setHTML) {
|
|
73
|
+
return _jsx(SetHTML, { value: value, className: className });
|
|
74
|
+
}
|
|
75
|
+
return (_jsx(Suspense, { fallback: _jsx("span", { className: className }), children: _jsx(DOMPurifySanitizedHTML, { value: value, className: className }) }));
|
|
61
76
|
}
|
package/esm/util/IntervalTree.js
CHANGED
|
@@ -107,13 +107,7 @@ export class IntervalTree {
|
|
|
107
107
|
const searchNode = new Node(interval);
|
|
108
108
|
const resultNodes = [];
|
|
109
109
|
this.treeSearchInterval(this.root, searchNode, resultNodes);
|
|
110
|
-
|
|
111
|
-
for (const node of resultNodes) {
|
|
112
|
-
for (const v of node.values) {
|
|
113
|
-
results.push(v);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return results;
|
|
110
|
+
return resultNodes.flatMap(node => node.values);
|
|
117
111
|
}
|
|
118
112
|
recalcMax(node) {
|
|
119
113
|
let current = node;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { stitch } from "../BaseFeatureWidget/util.js";
|
|
2
2
|
export function convertCodingSequenceToPeptides({ cds, sequence, codonTable, }) {
|
|
3
|
+
const phase = cds[0]?.phase ?? 0;
|
|
3
4
|
const str = stitch(cds, sequence);
|
|
4
|
-
let protein = '';
|
|
5
|
-
for (let i =
|
|
5
|
+
let protein = phase > 0 ? '&' : '';
|
|
6
|
+
for (let i = phase; i < str.length; i += 3) {
|
|
6
7
|
protein += codonTable[str.slice(i, i + 3)] || '&';
|
|
7
8
|
}
|
|
8
9
|
return protein;
|
package/esm/util/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare function useWidthSetter(view: {
|
|
|
23
23
|
setWidth: (arg: number) => void;
|
|
24
24
|
}, padding: string): React.RefObject<HTMLDivElement | null>;
|
|
25
25
|
export declare function useDebouncedCallback<T>(callback: (...args: T[]) => void, wait?: number): (...args: T[]) => void;
|
|
26
|
-
export declare function findParentThat(node: IAnyStateTreeNode, predicate: (thing: IAnyStateTreeNode) => boolean): IStateTreeNode<IAnyStateTreeNode>;
|
|
26
|
+
export declare function findParentThat(node: IAnyStateTreeNode, predicate: (thing: IAnyStateTreeNode) => boolean): (object & IStateTreeNode<import("@jbrowse/mobx-state-tree").IAnyComplexType>) | IStateTreeNode<IAnyStateTreeNode>;
|
|
27
27
|
export declare function springAnimate(fromValue: number, toValue: number, setValue: (value: number) => void, onFinish?: () => void, precision?: number, tension?: number, friction?: number, clamp?: boolean): (() => void)[];
|
|
28
28
|
export declare function findParentThatIs<T extends (a: IAnyStateTreeNode) => boolean>(node: IAnyStateTreeNode, predicate: T): TypeTestedByPredicate<T>;
|
|
29
29
|
export declare function getSession(node: IAnyStateTreeNode): AbstractSessionModel;
|
|
@@ -55,7 +55,7 @@ export declare function calculateLayoutBounds(featureStart: number, featureEnd:
|
|
|
55
55
|
export declare function iterMap<T, U>(iter: Iterable<T>, func: (arg: T) => U, sizeHint?: number): U[];
|
|
56
56
|
export declare function findLastIndex<T>(array: T[], predicate: (value: T, index: number, obj: T[]) => boolean): number;
|
|
57
57
|
export declare function findLast<T>(array: T[], predicate: (value: T, index: number, obj: T[]) => boolean): T | undefined;
|
|
58
|
-
export declare function renameRegionIfNeeded(refNameMap: Record<string, string> | undefined, region: Region | Instance<typeof MUIRegion
|
|
58
|
+
export declare function renameRegionIfNeeded(refNameMap: Record<string, string> | undefined, region: Region | Instance<typeof MUIRegion>, getSeqAdapterRefName?: (refName: string) => string): Region & {
|
|
59
59
|
originalRefName?: string;
|
|
60
60
|
};
|
|
61
61
|
export declare function renameRegionsIfNeeded<ARGTYPE extends {
|
package/esm/util/index.js
CHANGED
|
@@ -336,22 +336,17 @@ export function findLast(array, predicate) {
|
|
|
336
336
|
}
|
|
337
337
|
return undefined;
|
|
338
338
|
}
|
|
339
|
-
export function renameRegionIfNeeded(refNameMap, region) {
|
|
339
|
+
export function renameRegionIfNeeded(refNameMap, region, getSeqAdapterRefName) {
|
|
340
340
|
if (isStateTreeNode(region) && !isAlive(region)) {
|
|
341
341
|
return region;
|
|
342
342
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
...region,
|
|
351
|
-
refName: newRef,
|
|
352
|
-
originalRefName: region.refName,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
343
|
+
const newRef = refNameMap?.[region.refName];
|
|
344
|
+
if (newRef) {
|
|
345
|
+
return {
|
|
346
|
+
...(isStateTreeNode(region) ? getSnapshot(region) : region),
|
|
347
|
+
refName: newRef,
|
|
348
|
+
originalRefName: getSeqAdapterRefName?.(region.refName) ?? region.refName,
|
|
349
|
+
};
|
|
355
350
|
}
|
|
356
351
|
return region;
|
|
357
352
|
}
|
|
@@ -360,16 +355,21 @@ export async function renameRegionsIfNeeded(assemblyManager, args) {
|
|
|
360
355
|
if (!args.sessionId) {
|
|
361
356
|
throw new Error('sessionId is required');
|
|
362
357
|
}
|
|
363
|
-
const assemblyNames = regions.map(
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
358
|
+
const assemblyNames = regions.map(r => r.assemblyName);
|
|
359
|
+
const uniqueAssemblyNames = [...new Set(assemblyNames)];
|
|
360
|
+
const assemblyData = Object.fromEntries(await Promise.all(uniqueAssemblyNames.map(async (name) => [
|
|
361
|
+
name,
|
|
362
|
+
{
|
|
363
|
+
refNameMap: await assemblyManager.getRefNameMapForAdapter(adapterConfig, name, args),
|
|
364
|
+
assembly: assemblyManager.get(name),
|
|
365
|
+
},
|
|
366
|
+
])));
|
|
370
367
|
return {
|
|
371
368
|
...args,
|
|
372
|
-
regions: regions.map((region, i) =>
|
|
369
|
+
regions: regions.map((region, i) => {
|
|
370
|
+
const { refNameMap, assembly } = assemblyData[assemblyNames[i]];
|
|
371
|
+
return renameRegionIfNeeded(refNameMap, region, assembly ? r => assembly.getSeqAdapterRefName(r) : undefined);
|
|
372
|
+
}),
|
|
373
373
|
};
|
|
374
374
|
}
|
|
375
375
|
export function minmax(a, b) {
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { RemoteFile } from 'generic-filehandle2';
|
|
2
|
-
export interface BinaryRangeResponse {
|
|
3
|
-
headers: Record<string, string>;
|
|
4
|
-
requestDate: Date;
|
|
5
|
-
responseDate: Date;
|
|
6
|
-
buffer: Uint8Array;
|
|
7
|
-
}
|
|
8
2
|
export declare function clearCache(): void;
|
|
9
3
|
export declare class RemoteFileWithRangeCache extends RemoteFile {
|
|
4
|
+
private fetchRange;
|
|
5
|
+
private getCachedRange;
|
|
10
6
|
fetch(url: string | RequestInfo, init?: RequestInit): Promise<Response>;
|
|
11
|
-
fetchBinaryRange(url: string, start: number, end: number, options?: {
|
|
12
|
-
headers?: HeadersInit;
|
|
13
|
-
stopToken?: string;
|
|
14
|
-
}): Promise<BinaryRangeResponse>;
|
|
15
7
|
}
|
|
@@ -1,71 +1,119 @@
|
|
|
1
|
-
import { HttpRangeFetcher } from '@gmod/http-range-fetcher';
|
|
2
1
|
import { RemoteFile } from 'generic-filehandle2';
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
const MAX_CACHE_ENTRIES = 2000;
|
|
3
|
+
const CHUNK_SIZE = 256 * 1024;
|
|
4
|
+
const MAX_CONCURRENT = 20;
|
|
5
|
+
let cache = new Map();
|
|
6
|
+
let activeCount = 0;
|
|
7
|
+
const queue = [];
|
|
8
|
+
function getCached(key) {
|
|
9
|
+
return cache.get(key);
|
|
10
|
+
}
|
|
11
|
+
function putCached(key, buffer) {
|
|
12
|
+
if (cache.size >= MAX_CACHE_ENTRIES) {
|
|
13
|
+
const firstKey = cache.keys().next().value;
|
|
14
|
+
if (firstKey !== undefined) {
|
|
15
|
+
cache.delete(firstKey);
|
|
16
|
+
}
|
|
8
17
|
}
|
|
9
|
-
|
|
18
|
+
cache.set(key, buffer);
|
|
10
19
|
}
|
|
11
|
-
const globalRangeCache = new HttpRangeFetcher({
|
|
12
|
-
fetch: binaryRangeFetch,
|
|
13
|
-
size: 500 * 1024 ** 2,
|
|
14
|
-
chunkSize: 128 * 1024,
|
|
15
|
-
maxFetchSize: 100 * 1024 ** 2,
|
|
16
|
-
minimumTTL: 24 * 60 * 60 * 1000,
|
|
17
|
-
});
|
|
18
20
|
export function clearCache() {
|
|
19
|
-
|
|
21
|
+
cache = new Map();
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
function runNext() {
|
|
24
|
+
if (queue.length > 0 && activeCount < MAX_CONCURRENT) {
|
|
25
|
+
activeCount++;
|
|
26
|
+
const next = queue.shift();
|
|
27
|
+
next();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function limitConcurrency(fn) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
function run() {
|
|
33
|
+
fn().then(val => {
|
|
34
|
+
activeCount--;
|
|
35
|
+
resolve(val);
|
|
36
|
+
runNext();
|
|
37
|
+
}, (err) => {
|
|
38
|
+
activeCount--;
|
|
39
|
+
reject(err);
|
|
40
|
+
runNext();
|
|
41
|
+
});
|
|
26
42
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (rangeParse) {
|
|
31
|
-
const [, start, end] = rangeParse;
|
|
32
|
-
const s = Number.parseInt(start, 10);
|
|
33
|
-
const e = Number.parseInt(end, 10);
|
|
34
|
-
const len = e - s;
|
|
35
|
-
const { buffer, headers } = (await globalRangeCache.getRange(`${url}`, s, len + 1));
|
|
36
|
-
return new Response(buffer, {
|
|
37
|
-
status: 206,
|
|
38
|
-
headers,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
43
|
+
if (activeCount < MAX_CONCURRENT) {
|
|
44
|
+
activeCount++;
|
|
45
|
+
run();
|
|
41
46
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
else {
|
|
48
|
+
queue.push(run);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function cacheKey(url, chunkIndex) {
|
|
53
|
+
return `${url}:${chunkIndex}`;
|
|
54
|
+
}
|
|
55
|
+
export class RemoteFileWithRangeCache extends RemoteFile {
|
|
56
|
+
async fetchRange(url, start, end, signal) {
|
|
46
57
|
const res = await super.fetch(url, {
|
|
47
|
-
|
|
48
|
-
headers: {
|
|
49
|
-
...options.headers,
|
|
50
|
-
range: `bytes=${start}-${end}`,
|
|
51
|
-
},
|
|
58
|
+
signal: signal ?? undefined,
|
|
59
|
+
headers: { range: `bytes=${start}-${end}` },
|
|
52
60
|
});
|
|
53
|
-
const responseDate = new Date();
|
|
54
61
|
if (!res.ok) {
|
|
55
62
|
const errorMessage = `HTTP ${res.status} fetching ${url} bytes ${start}-${end}`;
|
|
56
63
|
const hint = ' (should be 206 for range requests)';
|
|
57
64
|
throw new Error(`${errorMessage}${res.status === 200 ? hint : ''}`);
|
|
58
65
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
67
|
+
}
|
|
68
|
+
async getCachedRange(url, start, length, signal) {
|
|
69
|
+
const startChunk = Math.floor(start / CHUNK_SIZE);
|
|
70
|
+
const endChunk = Math.floor((start + length - 1) / CHUNK_SIZE);
|
|
71
|
+
const chunkCount = endChunk - startChunk + 1;
|
|
72
|
+
const chunks = await Promise.all(Array.from({ length: chunkCount }, (_, i) => {
|
|
73
|
+
const idx = startChunk + i;
|
|
74
|
+
const key = cacheKey(url, idx);
|
|
75
|
+
const existing = getCached(key);
|
|
76
|
+
if (existing) {
|
|
77
|
+
return Promise.resolve(existing);
|
|
78
|
+
}
|
|
79
|
+
return limitConcurrency(async () => {
|
|
80
|
+
const alreadyCached = getCached(key);
|
|
81
|
+
if (alreadyCached) {
|
|
82
|
+
return alreadyCached;
|
|
83
|
+
}
|
|
84
|
+
const chunkStart = idx * CHUNK_SIZE;
|
|
85
|
+
const chunkEnd = chunkStart + CHUNK_SIZE - 1;
|
|
86
|
+
const data = await this.fetchRange(url, chunkStart, chunkEnd, signal);
|
|
87
|
+
putCached(key, data);
|
|
88
|
+
return data;
|
|
89
|
+
});
|
|
90
|
+
}));
|
|
91
|
+
const offsetInFirstChunk = start - startChunk * CHUNK_SIZE;
|
|
92
|
+
const result = new Uint8Array(length);
|
|
93
|
+
let written = 0;
|
|
94
|
+
for (const [i, chunk_] of chunks.entries()) {
|
|
95
|
+
const chunk = chunk_;
|
|
96
|
+
const sourceStart = i === 0 ? offsetInFirstChunk : 0;
|
|
97
|
+
const available = chunk.length - sourceStart;
|
|
98
|
+
const needed = length - written;
|
|
99
|
+
const copyLen = Math.min(available, needed);
|
|
100
|
+
result.set(chunk.subarray(sourceStart, sourceStart + copyLen), written);
|
|
101
|
+
written += copyLen;
|
|
102
|
+
}
|
|
103
|
+
return result.subarray(0, written);
|
|
104
|
+
}
|
|
105
|
+
async fetch(url, init) {
|
|
106
|
+
const range = new Headers(init?.headers).get('range');
|
|
107
|
+
if (range) {
|
|
108
|
+
const rangeParse = /bytes=(\d+)-(\d+)/.exec(range);
|
|
109
|
+
if (rangeParse) {
|
|
110
|
+
const [, startStr, endStr] = rangeParse;
|
|
111
|
+
const s = Number.parseInt(startStr, 10);
|
|
112
|
+
const e = Number.parseInt(endStr, 10);
|
|
113
|
+
const buffer = await this.getCachedRange(String(url), s, e - s + 1, init?.signal);
|
|
114
|
+
return new Response(buffer, { status: 206 });
|
|
115
|
+
}
|
|
62
116
|
}
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
headers,
|
|
66
|
-
requestDate,
|
|
67
|
-
responseDate,
|
|
68
|
-
buffer: new Uint8Array(arrayBuffer),
|
|
69
|
-
};
|
|
117
|
+
return super.fetch(url, init);
|
|
70
118
|
}
|
|
71
119
|
}
|
|
@@ -7,10 +7,10 @@ import type { BaseInternetAccountModel } from '../../pluggableElementTypes/model
|
|
|
7
7
|
import type RpcManager from '../../rpc/RpcManager.ts';
|
|
8
8
|
import type { MenuItem } from '../../ui/index.ts';
|
|
9
9
|
import type { Feature } from '../simpleFeature.ts';
|
|
10
|
-
import type { IAnyStateTreeNode, IStateTreeNode,
|
|
10
|
+
import type { IAnyStateTreeNode, IStateTreeNode, Instance, SnapshotIn } from '@jbrowse/mobx-state-tree';
|
|
11
11
|
import type { ThemeOptions } from '@mui/material';
|
|
12
12
|
export * from './util.ts';
|
|
13
|
-
export interface AbstractViewContainer extends IStateTreeNode
|
|
13
|
+
export interface AbstractViewContainer extends IStateTreeNode {
|
|
14
14
|
views: AbstractViewModel[];
|
|
15
15
|
removeView(view: AbstractViewModel): void;
|
|
16
16
|
addView(typeName: string, initialState?: Record<string, unknown>): AbstractViewModel;
|
|
@@ -70,11 +70,11 @@ export interface AbstractSessionModel extends AbstractViewContainer {
|
|
|
70
70
|
getTrackActionMenuItems?: (config: AnyConfigurationModel, extraTrackActions?: MenuItem[]) => MenuItem[];
|
|
71
71
|
getTrackActions?: (arg: AnyConfigurationModel) => MenuItem[];
|
|
72
72
|
getTrackListMenuItems?: (arg: AnyConfigurationModel) => MenuItem[];
|
|
73
|
-
addAssembly?:
|
|
74
|
-
removeAssembly?:
|
|
73
|
+
addAssembly?: (conf: Record<string, unknown>) => void;
|
|
74
|
+
removeAssembly?: (name: string) => void;
|
|
75
75
|
textSearchManager?: TextSearchManager;
|
|
76
76
|
connections: AnyConfigurationModel[];
|
|
77
|
-
deleteConnection?:
|
|
77
|
+
deleteConnection?: (arg: AnyConfigurationModel) => void;
|
|
78
78
|
temporaryAssemblies?: unknown[];
|
|
79
79
|
addTemporaryAssembly?: (arg: Record<string, unknown>) => void;
|
|
80
80
|
removeTemporaryAssembly?: (arg: string) => void;
|
|
@@ -85,14 +85,16 @@ export interface AbstractSessionModel extends AbstractViewContainer {
|
|
|
85
85
|
tracks: AnyConfigurationModel[];
|
|
86
86
|
configuration: AnyConfigurationModel;
|
|
87
87
|
}[];
|
|
88
|
-
makeConnection?:
|
|
89
|
-
breakConnection?:
|
|
90
|
-
prepareToBreakConnection?: (arg: AnyConfigurationModel) =>
|
|
88
|
+
makeConnection?: (arg: AnyConfigurationModel) => void;
|
|
89
|
+
breakConnection?: (arg: AnyConfigurationModel) => void;
|
|
90
|
+
prepareToBreakConnection?: (arg: AnyConfigurationModel) => [() => void, Record<string, number>] | undefined;
|
|
91
91
|
adminMode?: boolean;
|
|
92
|
-
showWidget?:
|
|
93
|
-
addWidget?:
|
|
92
|
+
showWidget?: (widget: unknown) => void;
|
|
93
|
+
addWidget?: (typeName: string, id: string, initialState?: Record<string, unknown>, configuration?: {
|
|
94
|
+
type: string;
|
|
95
|
+
}) => Widget;
|
|
94
96
|
DialogComponent?: DialogComponentType;
|
|
95
|
-
DialogProps:
|
|
97
|
+
DialogProps: Record<string, unknown> | undefined;
|
|
96
98
|
queueDialog<T extends DialogComponentType>(callback: (doneCallback: () => void) => [T, React.ComponentProps<T>]): void;
|
|
97
99
|
name: string;
|
|
98
100
|
id?: string;
|
|
@@ -156,8 +158,8 @@ export interface SessionWithConnectionEditing {
|
|
|
156
158
|
export declare function isSessionModelWithConnectionEditing(thing: unknown): thing is SessionWithConnectionEditing;
|
|
157
159
|
export interface SessionWithSessionPlugins extends AbstractSessionModel {
|
|
158
160
|
sessionPlugins: JBrowsePlugin[];
|
|
159
|
-
addSessionPlugin:
|
|
160
|
-
removeSessionPlugin:
|
|
161
|
+
addSessionPlugin: (plugin: BasePlugin) => void;
|
|
162
|
+
removeSessionPlugin: (plugin: Record<string, unknown> | undefined) => void;
|
|
161
163
|
}
|
|
162
164
|
export declare function isSessionWithSessionPlugins(thing: unknown): thing is SessionWithSessionPlugins;
|
|
163
165
|
export interface SelectionContainer extends AbstractSessionModel {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/core",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 core libraries used by plugins",
|
|
6
6
|
"keywords": [
|
|
@@ -476,30 +476,30 @@
|
|
|
476
476
|
"@emotion/react": "^11.14.0",
|
|
477
477
|
"@emotion/serialize": "^1.3.3",
|
|
478
478
|
"@emotion/utils": "^1.4.2",
|
|
479
|
-
"@floating-ui/react": "^0.27.
|
|
479
|
+
"@floating-ui/react": "^0.27.19",
|
|
480
480
|
"@gmod/abortable-promise-cache": "^3.0.4",
|
|
481
|
-
"@gmod/bgzf-filehandle": "^6.0.
|
|
481
|
+
"@gmod/bgzf-filehandle": "^6.0.18",
|
|
482
482
|
"@gmod/http-range-fetcher": "^5.0.7",
|
|
483
|
-
"@jbrowse/jexl": "^3.0.
|
|
484
|
-
"@jbrowse/mobx-state-tree": "^5.
|
|
483
|
+
"@jbrowse/jexl": "^3.0.2",
|
|
484
|
+
"@jbrowse/mobx-state-tree": "^5.6.0",
|
|
485
485
|
"@jbrowse/quick-lru": "^7.3.5",
|
|
486
486
|
"@leeoniya/ufuzzy": "^1.0.19",
|
|
487
|
-
"@mui/icons-material": "^7.3.
|
|
488
|
-
"@mui/material": "^7.3.
|
|
489
|
-
"@mui/system": "^7.3.
|
|
490
|
-
"@mui/types": "^7.4.
|
|
491
|
-
"@mui/x-data-grid": "^8.
|
|
487
|
+
"@mui/icons-material": "^7.3.9",
|
|
488
|
+
"@mui/material": "^7.3.9",
|
|
489
|
+
"@mui/system": "^7.3.9",
|
|
490
|
+
"@mui/types": "^7.4.12",
|
|
491
|
+
"@mui/x-data-grid": "^8.28.2",
|
|
492
492
|
"@types/file-saver-es": "^2.0.3",
|
|
493
493
|
"canvas-sequencer-ts": "^3.1.3",
|
|
494
494
|
"canvas2svg": "^1.0.16",
|
|
495
495
|
"copy-to-clipboard": "^3.3.3",
|
|
496
496
|
"deepmerge": "^4.3.1",
|
|
497
497
|
"detect-node": "^2.1.0",
|
|
498
|
-
"dompurify": "^3.3.
|
|
498
|
+
"dompurify": "^3.3.3",
|
|
499
499
|
"escape-html": "^1.0.3",
|
|
500
500
|
"fast-deep-equal": "^3.1.3",
|
|
501
501
|
"file-saver-es": "^2.0.5",
|
|
502
|
-
"generic-filehandle2": "^2.
|
|
502
|
+
"generic-filehandle2": "^2.1.4",
|
|
503
503
|
"idb": "^8.0.3",
|
|
504
504
|
"librpc-web-mod": "^2.1.1",
|
|
505
505
|
"mobx": "^6.15.0",
|