@rivm-loculus-tools/common-frontend 0.1.4 → 0.2.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/utils/DownloadUtil/DownloadUtil.ts"],"sourcesContent":["export class DownloadUtil {\n public static datasetFilename(label: string): string {\n const withoutExtension = label.trim().replace(/\\.[^.]+$/, '');\n const safeLabel = withoutExtension\n .replace(/[^a-zA-Z0-9._-]+/g, '_')\n .replace(/^\\.+|\\.+$/g, '')\n .slice(0, 80);\n return `${safeLabel || 'alignment'}.fasta`;\n }\n}\n"],"mappings":";AAAA,IAAa,IAAb,MAA0B;CACxB,OAAc,gBAAgB,GAAuB;EAMnD,OAAO,GALkB,EAAM,KAAK,CAAC,CAAC,QAAQ,YAAY,EACxC,CAAA,CACf,QAAQ,qBAAqB,GAAG,CAAC,CACjC,QAAQ,cAAc,EAAE,CAAC,CACzB,MAAM,GAAG,EACF,KAAa,YAAY;CACrC;AACF"}
1
+ {"version":3,"file":"index.js","names":["DownloadUtil","label"],"sources":["../../../src/utils/DownloadUtil/DownloadUtil.ts"],"sourcesContent":["export class DownloadUtil {\n public static datasetFilename(label: string): string {\n const withoutExtension = label.trim().replace(/\\.[^.]+$/, '');\n const safeLabel = withoutExtension\n .replace(/[^a-zA-Z0-9._-]+/g, '_')\n .replace(/^\\.+|\\.+$/g, '')\n .slice(0, 80);\n return `${safeLabel || 'alignment'}.fasta`;\n }\n}\n"],"mappings":";AAAA,IAAaA,IAAb,MAA0B;CACxB,OAAc,gBAAgB,GAAuB;EAMnD,OAAO,GALkBC,EAAM,KAAK,CAAC,CAAC,QAAQ,YAAY,EACxC,CAAA,CACf,QAAQ,qBAAqB,GAAG,CAAC,CACjC,QAAQ,cAAc,EAAE,CAAC,CACzB,MAAM,GAAG,EACF,KAAa,YAAY;CACrC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/utils/ErrorUtil/ErrorUtil.ts"],"sourcesContent":["export class ErrorUtil {\n public static getErrorMessage(error: unknown, fallback: string): string {\n return error instanceof Error ? error.message : fallback;\n }\n}\n"],"mappings":";AAAA,IAAa,IAAb,MAAuB;CACrB,OAAc,gBAAgB,GAAgB,GAA0B;EACtE,OAAO,aAAiB,QAAQ,EAAM,UAAU;CAClD;AACF"}
1
+ {"version":3,"file":"index.js","names":["ErrorUtil","error","fallback"],"sources":["../../../src/utils/ErrorUtil/ErrorUtil.ts"],"sourcesContent":["export class ErrorUtil {\n public static getErrorMessage(error: unknown, fallback: string): string {\n return error instanceof Error ? error.message : fallback;\n }\n}\n"],"mappings":";AAAA,IAAaA,IAAb,MAAuB;CACrB,OAAc,gBAAgB,GAAgB,GAA0B;EACtE,OAAOC,aAAiB,QAAQA,EAAM,UAAUC;CAClD;AACF"}
@@ -4,26 +4,21 @@ var e = class e {
4
4
  if (!t?.trim()) return e.createErrorResult("The FASTA input is empty.");
5
5
  let n = t.split(/\r?\n/), r = [], i = "", a = [], o = () => {
6
6
  if (i) {
7
- let t = a.join("").replace(/\s/g, "").toUpperCase();
8
- if (!t.length) return e.createErrorResult(`FASTA record "${i}" has no sequence.`);
9
- let n = i.split(/[|\t]/)[0].trim().replace(/[():;,']/g, "_");
7
+ let e = a.join("").replace(/\s/g, "").toUpperCase(), t = i.split(/[|\t]/)[0].trim().replace(/[():;,']/g, "_");
10
8
  r.push({
11
- accessionVersion: n,
9
+ accessionVersion: t,
12
10
  header: i,
13
- id: n,
14
- length: t.length,
15
- sequence: t
11
+ id: t,
12
+ length: e.length,
13
+ sequence: e
16
14
  });
17
15
  }
18
- return null;
19
16
  };
20
17
  for (let t of n) {
21
18
  let n = t.trim();
22
19
  if (n) {
23
20
  if (n.startsWith(">")) {
24
- let t = o();
25
- if (t) return t;
26
- if (i = n.slice(1).trim(), !i) return e.createErrorResult("Every FASTA record needs a non-empty header.");
21
+ if (o(), i = n.slice(1).trim(), !i) return e.createErrorResult("Every FASTA record needs a non-empty header.");
27
22
  a = [];
28
23
  } else {
29
24
  if (!i) return e.createErrorResult("FASTA sequence data appeared before its first header.");
@@ -31,17 +26,15 @@ var e = class e {
31
26
  }
32
27
  }
33
28
  }
34
- let s = o();
35
- if (s) return s;
36
- if (r.length === 0) return e.createErrorResult("No FASTA records were found.");
37
- let c = r[0].length, l = r.filter((e) => e.length !== c).map((e) => ({
29
+ if (o(), r.length === 0) return e.createErrorResult("No FASTA records were found.");
30
+ let s = r[0].length, c = r.filter((e) => e.length !== s).map((e) => ({
38
31
  id: e.id,
39
32
  length: e.length
40
- })), u = l.length === 0;
33
+ })), l = c.length === 0;
41
34
  return {
42
- expectedLength: c,
43
- invalidSequences: u ? void 0 : l,
44
- isAligned: u,
35
+ expectedLength: s,
36
+ invalidSequences: l ? void 0 : c,
37
+ isAligned: l,
45
38
  sequences: r
46
39
  };
47
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/utils/FastaUtil/FastaUtil.ts"],"sourcesContent":["import type {\n FastaParseResult,\n FastaSequence,\n} from '../../models/fasta';\n\nexport class FastaUtil {\n public static parseFasta(content: string): FastaParseResult {\n if (!content?.trim()) {\n return FastaUtil.createErrorResult('The FASTA input is empty.');\n }\n\n const lines = content.split(/\\r?\\n/);\n const sequences: FastaSequence[] = [];\n let currentHeader = '';\n let currentSeqParts: string[] = [];\n\n const commitSequence = (): FastaParseResult | null => {\n if (currentHeader) {\n const fullSeq = currentSeqParts.join('').replace(/\\s/g, '').toUpperCase();\n if (!fullSeq.length) {\n return FastaUtil.createErrorResult(`FASTA record \"${currentHeader}\" has no sequence.`);\n }\n\n // Extract ID: part before pipe or tab, sanitized for Newick compatibility\n const firstPart = currentHeader.split(/[|\\t]/)[0].trim();\n const safeId = firstPart.replace(/[():;,']/g, '_');\n\n sequences.push({\n accessionVersion: safeId,\n header: currentHeader,\n id: safeId,\n length: fullSeq.length,\n sequence: fullSeq,\n });\n }\n return null;\n };\n\n for (const sourceLine of lines) {\n const line = sourceLine.trim();\n if (!line) {\n continue;\n }\n\n if (line.startsWith('>')) {\n const error = commitSequence();\n if (error) {\n return error;\n }\n currentHeader = line.slice(1).trim();\n if (!currentHeader) {\n return FastaUtil.createErrorResult('Every FASTA record needs a non-empty header.');\n }\n currentSeqParts = [];\n } else {\n if (!currentHeader) {\n return FastaUtil.createErrorResult('FASTA sequence data appeared before its first header.');\n }\n currentSeqParts.push(line);\n }\n }\n const error = commitSequence();\n if (error) {\n return error;\n }\n\n if (sequences.length === 0) {\n return FastaUtil.createErrorResult('No FASTA records were found.');\n }\n\n // Pre-alignment validation: verify all sequence lengths are equal\n const firstLength = sequences[0].length;\n const invalidSequences = sequences.filter((s) => s.length !== firstLength).map((s) => ({ id: s.id, length: s.length }));\n\n const isAligned = invalidSequences.length === 0;\n\n return {\n expectedLength: firstLength,\n invalidSequences: isAligned ? undefined : invalidSequences,\n isAligned,\n sequences,\n };\n }\n\n public static stripFastaHeaders(fastaContent: string): string {\n const lines = fastaContent.split('\\n');\n const strippedLines = lines.map((line) => {\n if (line.startsWith('>')) {\n const cleanHeader = line.substring(1).trim();\n const firstPart = cleanHeader.split(/[|\\t]/)[0].trim();\n const safeId = firstPart.replace(/[():;,']/g, '_');\n return `>${safeId}`;\n }\n return line;\n });\n return strippedLines.join('\\n');\n }\n\n\n private static createErrorResult(error: string): FastaParseResult {\n return {\n error,\n expectedLength: 0,\n isAligned: false,\n sequences: [],\n };\n }\n\n}\n"],"mappings":";AAKA,IAAa,IAAb,MAAa,EAAU;CACrB,OAAc,WAAW,GAAmC;EAC1D,IAAI,CAAC,GAAS,KAAK,GACjB,OAAO,EAAU,kBAAkB,2BAA2B;EAGhE,IAAM,IAAQ,EAAQ,MAAM,OAAO,GAC7B,IAA6B,CAAC,GAChC,IAAgB,IAChB,IAA4B,CAAC,GAE3B,UAAgD;GACpD,IAAI,GAAe;IACjB,IAAM,IAAU,EAAgB,KAAK,EAAE,CAAC,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAC,YAAY;IACxE,IAAI,CAAC,EAAQ,QACX,OAAO,EAAU,kBAAkB,iBAAiB,EAAc,mBAAmB;IAKvF,IAAM,IADY,EAAc,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,KACnC,CAAA,CAAU,QAAQ,aAAa,GAAG;IAEjD,EAAU,KAAK;KACb,kBAAkB;KAClB,QAAQ;KACR,IAAI;KACJ,QAAQ,EAAQ;KAChB,UAAU;IACZ,CAAC;GACH;GACA,OAAO;EACT;EAEA,KAAK,IAAM,KAAc,GAAO;GAC9B,IAAM,IAAO,EAAW,KAAK;GACxB,OAIL;QAAI,EAAK,WAAW,GAAG,GAAG;KACxB,IAAM,IAAQ,EAAe;KAC7B,IAAI,GACF,OAAO;KAGT,IADA,IAAgB,EAAK,MAAM,CAAC,CAAC,CAAC,KAAK,GAC/B,CAAC,GACH,OAAO,EAAU,kBAAkB,8CAA8C;KAEnF,IAAkB,CAAC;IACrB,OAAO;KACL,IAAI,CAAC,GACH,OAAO,EAAU,kBAAkB,uDAAuD;KAE5F,EAAgB,KAAK,CAAI;IAC3B;;EACF;EACA,IAAM,IAAQ,EAAe;EAC7B,IAAI,GACF,OAAO;EAGT,IAAI,EAAU,WAAW,GACvB,OAAO,EAAU,kBAAkB,8BAA8B;EAInE,IAAM,IAAc,EAAU,EAAE,CAAC,QAC3B,IAAmB,EAAU,QAAQ,MAAM,EAAE,WAAW,CAAW,CAAC,CAAC,KAAK,OAAO;GAAE,IAAI,EAAE;GAAI,QAAQ,EAAE;EAAO,EAAE,GAEhH,IAAY,EAAiB,WAAW;EAE9C,OAAO;GACL,gBAAgB;GAChB,kBAAkB,IAAY,KAAA,IAAY;GAC1C;GACA;EACF;CACF;CAEA,OAAc,kBAAkB,GAA8B;EAW5D,OAVc,EAAa,MAAM,IACX,CAAA,CAAM,KAAK,MAC3B,EAAK,WAAW,GAAG,IAId,IAHa,EAAK,UAAU,CAAC,CAAC,CAAC,KACpB,CAAA,CAAY,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,KACjC,CAAA,CAAU,QAAQ,aAAa,GACnC,MAEN,CAEF,CAAA,CAAc,KAAK,IAAI;CAChC;CAGA,OAAe,kBAAkB,GAAiC;EAChE,OAAO;GACL;GACA,gBAAgB;GAChB,WAAW;GACX,WAAW,CAAC;EACd;CACF;AAEF"}
1
+ {"version":3,"file":"index.js","names":["FastaUtil","content","lines","sequences","currentHeader","currentSeqParts","commitSequence","fullSeq","safeId","sourceLine","line","firstLength","invalidSequences","s","isAligned","fastaContent"],"sources":["../../../src/utils/FastaUtil/FastaUtil.ts"],"sourcesContent":["import type {\n FastaParseResult,\n FastaSequence,\n} from '../../models/fasta';\n\nexport class FastaUtil {\n public static parseFasta(content: string): FastaParseResult {\n if (!content?.trim()) {\n return FastaUtil.createErrorResult('The FASTA input is empty.');\n }\n\n const lines = content.split(/\\r?\\n/);\n const sequences: FastaSequence[] = [];\n let currentHeader = '';\n let currentSeqParts: string[] = [];\n\n const commitSequence = (): void => {\n if (currentHeader) {\n const fullSeq = currentSeqParts.join('').replace(/\\s/g, '').toUpperCase();\n\n // Empty records (a header with no following sequence) are retained with\n // a length of zero rather than being discarded or rejected.\n\n // Extract ID: part before pipe or tab, sanitized for Newick compatibility\n const firstPart = currentHeader.split(/[|\\t]/)[0].trim();\n const safeId = firstPart.replace(/[():;,']/g, '_');\n\n sequences.push({\n accessionVersion: safeId,\n header: currentHeader,\n id: safeId,\n length: fullSeq.length,\n sequence: fullSeq,\n });\n }\n };\n\n for (const sourceLine of lines) {\n const line = sourceLine.trim();\n if (!line) {\n continue;\n }\n\n if (line.startsWith('>')) {\n commitSequence();\n currentHeader = line.slice(1).trim();\n if (!currentHeader) {\n return FastaUtil.createErrorResult('Every FASTA record needs a non-empty header.');\n }\n currentSeqParts = [];\n } else {\n if (!currentHeader) {\n return FastaUtil.createErrorResult('FASTA sequence data appeared before its first header.');\n }\n currentSeqParts.push(line);\n }\n }\n commitSequence();\n\n if (sequences.length === 0) {\n return FastaUtil.createErrorResult('No FASTA records were found.');\n }\n\n // Pre-alignment validation: verify all sequence lengths are equal\n const firstLength = sequences[0].length;\n const invalidSequences = sequences.filter((s) => s.length !== firstLength).map((s) => ({ id: s.id, length: s.length }));\n\n const isAligned = invalidSequences.length === 0;\n\n return {\n expectedLength: firstLength,\n invalidSequences: isAligned ? undefined : invalidSequences,\n isAligned,\n sequences,\n };\n }\n\n public static stripFastaHeaders(fastaContent: string): string {\n const lines = fastaContent.split('\\n');\n const strippedLines = lines.map((line) => {\n if (line.startsWith('>')) {\n const cleanHeader = line.substring(1).trim();\n const firstPart = cleanHeader.split(/[|\\t]/)[0].trim();\n const safeId = firstPart.replace(/[():;,']/g, '_');\n return `>${safeId}`;\n }\n return line;\n });\n return strippedLines.join('\\n');\n }\n\n\n private static createErrorResult(error: string): FastaParseResult {\n return {\n error,\n expectedLength: 0,\n isAligned: false,\n sequences: [],\n };\n }\n\n}\n"],"mappings":";AAKA,IAAaA,IAAb,MAAaA,EAAU;CACrB,OAAc,WAAW,GAAmC;EAC1D,IAAI,CAACC,GAAS,KAAK,GACjB,OAAOD,EAAU,kBAAkB,2BAA2B;EAGhE,IAAME,IAAQD,EAAQ,MAAM,OAAO,GAC7BE,IAA6B,CAAC,GAChCC,IAAgB,IAChBC,IAA4B,CAAC,GAE3BC,UAA6B;GACjC,IAAIF,GAAe;IACjB,IAAMG,IAAUF,EAAgB,KAAK,EAAE,CAAC,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAC,YAAY,GAOlEG,IADYJ,EAAc,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,KACnC,CAAA,CAAU,QAAQ,aAAa,GAAG;IAEjD,EAAU,KAAK;KACb,kBAAkBI;KAClB,QAAQJ;KACR,IAAII;KACJ,QAAQD,EAAQ;KAChB,UAAUA;IACZ,CAAC;GACH;EACF;EAEA,KAAK,IAAME,KAAcP,GAAO;GAC9B,IAAMQ,IAAOD,EAAW,KAAK;GAC7B,IAAKC,GAIL;QAAIA,EAAK,WAAW,GAAG,GAAG;KAGxB,IAFAJ,EAAe,GACf,IAAgBI,EAAK,MAAM,CAAC,CAAC,CAAC,KAAK,GAC/B,CAACN,GACH,OAAOJ,EAAU,kBAAkB,8CAA8C;KAEnF,IAAkB,CAAC;IACrB,OAAO;KACL,IAAI,CAACI,GACH,OAAOJ,EAAU,kBAAkB,uDAAuD;KAE5F,EAAgB,KAAKU,CAAI;IAC3B;;EACF;EAGA,IAFAJ,EAAe,GAEXH,EAAU,WAAW,GACvB,OAAOH,EAAU,kBAAkB,8BAA8B;EAInE,IAAMW,IAAcR,EAAU,EAAE,CAAC,QAC3BS,IAAmBT,EAAU,QAAQ,MAAMU,EAAE,WAAWF,CAAW,CAAC,CAAC,KAAK,OAAO;GAAE,IAAIE,EAAE;GAAI,QAAQA,EAAE;EAAO,EAAE,GAEhHC,IAAYF,EAAiB,WAAW;EAE9C,OAAO;GACL,gBAAgBD;GAChB,kBAAkBG,IAAY,KAAA,IAAYF;GAC1C;GACA;EACF;CACF;CAEA,OAAc,kBAAkB,GAA8B;EAW5D,OAVcG,EAAa,MAAM,IACX,CAAA,CAAM,KAAK,MAC3BL,EAAK,WAAW,GAAG,IAId,IAHaA,EAAK,UAAU,CAAC,CAAC,CAAC,KACpB,CAAA,CAAY,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,KACjC,CAAA,CAAU,QAAQ,aAAa,GACnC,MAENA,CAEF,CAAA,CAAc,KAAK,IAAI;CAChC;CAGA,OAAe,kBAAkB,GAAiC;EAChE,OAAO;GACL;GACA,gBAAgB;GAChB,WAAW;GACX,WAAW,CAAC;EACd;CACF;AAEF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/utils/MetaDataUtil/MetaDataUtil.ts"],"sourcesContent":["import type {\n MetadataParseResult,\n MetadataRecord,\n} from '../../models/metadata';\n\nexport class MetaDataUtil {\n /**\n * Compute the best matching metadata column for the given FASTA sequence IDs.\n */\n public static findBestMatchingColumn = (\n columns: string[],\n recordsByColumn: Record<string, Record<string, MetadataRecord>>,\n fastaIds: string[],\n ): { bestColumn: null | string; matchCounts: Record<string, number> } => {\n const matchCounts = MetaDataUtil.getMatchCounts(columns, recordsByColumn, fastaIds);\n let bestColumn: null | string = null;\n let maxMatches = -1;\n\n if (!columns || !recordsByColumn || !fastaIds || fastaIds.length === 0) {\n return { bestColumn: null, matchCounts: {} };\n }\n\n const prioritizedKeys = [\n 'displayname',\n 'accessionversion',\n 'accession',\n 'sampleid',\n 'sample_id',\n 'id',\n 'strain',\n 'isolate',\n 'submissionid',\n 'name',\n ];\n\n for (const col of columns) {\n const colDict = recordsByColumn[col];\n if (!colDict) {\n continue;\n }\n\n const count = matchCounts[col];\n\n if (count > maxMatches) {\n maxMatches = count;\n bestColumn = col;\n } else if (count === maxMatches && count > 0 && bestColumn) {\n // Tie-breaker: prioritize standard ID column names\n const currentPriority = prioritizedKeys.indexOf(bestColumn.toLowerCase());\n const newPriority = prioritizedKeys.indexOf(col.toLowerCase());\n if (newPriority !== -1 && (currentPriority === -1 || newPriority < currentPriority)) {\n bestColumn = col;\n }\n }\n }\n\n if (maxMatches <= 0) {\n // If no matches, default to highest priority known ID column if available\n for (const pKey of prioritizedKeys) {\n const found = columns.find((c) => c.toLowerCase() === pKey);\n if (found) {\n bestColumn = found;\n break;\n }\n }\n if (!bestColumn && columns.length > 0) {\n bestColumn = columns[0];\n }\n }\n\n return { bestColumn, matchCounts };\n };\n\n public static getHighlightedIds(query: string, allAccessions: string[], metadataRecords: MetadataRecord[], matchKeyField: null | string = null): string[] {\n const trimmed = query.trim().toLowerCase();\n if (!trimmed) {\n return [];\n }\n\n const matched = new Set<string>();\n\n // Check accession names / fasta IDs\n allAccessions.forEach((acc) => {\n if (acc.toLowerCase().includes(trimmed)) {\n matched.add(acc);\n }\n });\n\n // Check metadata records\n metadataRecords.forEach((rec) => {\n const acc =\n (matchKeyField && rec[matchKeyField]) ||\n rec.displayName ||\n rec.accessionVersion ||\n rec.accession ||\n rec.id ||\n rec.submissionId ||\n rec.SampleID;\n if (acc) {\n const matchesAnyField = Object.values(rec).some(\n (val) => typeof val === 'string' && val.toLowerCase().includes(trimmed),\n );\n if (matchesAnyField) {\n matched.add(acc);\n }\n }\n });\n\n return Array.from(matched);\n }\n /**\n * Count FASTA sequence ID matches for each metadata column.\n */\n public static getMatchCounts = (\n columns: string[],\n recordsByColumn: Record<string, Record<string, MetadataRecord>>,\n fastaIds: string[],\n ): Record<string, number> => {\n const matchCounts: Record<string, number> = {};\n\n if (!columns || !recordsByColumn || !fastaIds || fastaIds.length === 0) {\n return matchCounts;\n }\n\n for (const col of columns) {\n const colDict = recordsByColumn[col];\n if (!colDict) {\n continue;\n }\n\n let count = 0;\n for (const id of fastaIds) {\n if (colDict[id]) {\n count++;\n }\n }\n matchCounts[col] = count;\n }\n\n return matchCounts;\n };\n\n /**\n * Retrieve metadata record for a given sample/node name with fallback strategies.\n */\n public static readonly getRecordForSample = (\n metadata: MetadataParseResult | null | undefined,\n sampleId: string,\n matchKeyField?: null | string,\n ): MetadataRecord | undefined => {\n if (!metadata || !sampleId) {\n return undefined;\n }\n\n // 1. If matchKeyField is specified and exists, check it first\n if (matchKeyField && metadata.recordsByColumn?.[matchKeyField]) {\n const rec = metadata.recordsByColumn[matchKeyField][sampleId];\n if (rec) {\n return rec;\n }\n }\n\n // 2. Check displayName\n if (metadata.recordsByColumn?.['displayName']?.[sampleId]) {\n return metadata.recordsByColumn['displayName'][sampleId];\n }\n\n // 3. Check accessionVersion\n if (metadata.recordsByColumn?.['accessionVersion']?.[sampleId]) {\n return metadata.recordsByColumn['accessionVersion'][sampleId];\n }\n\n // 4. Check accession maps\n if (metadata.recordsByAccession[sampleId]) {\n return metadata.recordsByAccession[sampleId];\n }\n if (metadata.recordsBySampleId[sampleId]) {\n return metadata.recordsBySampleId[sampleId];\n }\n\n // 5. Broad search through all indexed columns\n if (metadata.recordsByColumn) {\n for (const col of Object.keys(metadata.recordsByColumn)) {\n const match = metadata.recordsByColumn[col][sampleId];\n if (match) {\n return match;\n }\n }\n }\n\n return undefined;\n };\n\n public static parseTsv = (content: string): MetadataParseResult => {\n if (!content?.trim()) {\n return {\n columns: [],\n error: 'Empty metadata content provided.',\n records: [],\n recordsByAccession: {},\n recordsByColumn: {},\n recordsBySampleId: {},\n };\n }\n\n const lines = content.trim().split('\\n');\n if (lines.length < 2) {\n return {\n columns: [],\n error: 'Metadata file must contain at least a header row and one data row.',\n records: [],\n recordsByAccession: {},\n recordsByColumn: {},\n recordsBySampleId: {},\n };\n }\n\n // Parse header row\n const headerLine = lines[0].trim();\n let delimiter = '\\t';\n if (!headerLine.includes('\\t') && headerLine.includes(',')) {\n delimiter = ',';\n }\n const columns = headerLine\n .split(delimiter)\n .map((col) => col.trim().replace(/^[\"']|[\"']$/g, ''))\n .filter((c) => c.length > 0);\n\n const records: MetadataRecord[] = [];\n const recordsByAccession: Record<string, MetadataRecord> = {};\n const recordsBySampleId: Record<string, MetadataRecord> = {};\n const recordsByColumn: Record<string, Record<string, MetadataRecord>> = {};\n\n columns.forEach((col) => {\n recordsByColumn[col] = {};\n });\n\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i].trim();\n if (!line) {\n continue;\n }\n\n const values = line.split(delimiter).map((val) => val.trim().replace(/^[\"']|[\"']$/g, ''));\n const record: MetadataRecord = {};\n\n columns.forEach((col, colIdx) => {\n const val = values[colIdx] ?? '';\n record[col] = val;\n if (val) {\n recordsByColumn[col][val] = record;\n }\n });\n\n records.push(record);\n\n // Index by potential ID keys\n const accessionKey =\n record.displayName ||\n record.accessionVersion ||\n record.accession ||\n record.id ||\n record.submissionId ||\n record.strain ||\n record.isolate ||\n record.name;\n\n if (accessionKey) {\n recordsByAccession[accessionKey] = record;\n }\n\n const sampleIdKey =\n record.SampleID ||\n record.sampleId ||\n record.sample_id ||\n record.specimenCollectorSampleId ||\n record.id;\n\n if (sampleIdKey) {\n recordsBySampleId[sampleIdKey] = record;\n }\n }\n\n return {\n columns,\n records,\n recordsByAccession,\n recordsByColumn,\n recordsBySampleId,\n };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["MetaDataUtil","matchCounts","columns","recordsByColumn","fastaIds","bestColumn","maxMatches","prioritizedKeys","col","count","currentPriority","newPriority","pKey","found","c","trimmed","query","matched","allAccessions","acc","metadataRecords","matchKeyField","rec","val","colDict","id","metadata","sampleId","\"displayName\"","\"accessionVersion\"","match","content","lines","headerLine","delimiter","records","recordsByAccession","recordsBySampleId","i","line","values","record","colIdx","accessionKey","sampleIdKey"],"sources":["../../../src/utils/MetaDataUtil/MetaDataUtil.ts"],"sourcesContent":["import type {\n MetadataParseResult,\n MetadataRecord,\n} from '../../models/metadata';\n\nexport class MetaDataUtil {\n /**\n * Compute the best matching metadata column for the given FASTA sequence IDs.\n */\n public static findBestMatchingColumn = (\n columns: string[],\n recordsByColumn: Record<string, Record<string, MetadataRecord>>,\n fastaIds: string[],\n ): { bestColumn: null | string; matchCounts: Record<string, number> } => {\n const matchCounts = MetaDataUtil.getMatchCounts(columns, recordsByColumn, fastaIds);\n let bestColumn: null | string = null;\n let maxMatches = -1;\n\n if (!columns || !recordsByColumn || !fastaIds || fastaIds.length === 0) {\n return { bestColumn: null, matchCounts: {} };\n }\n\n const prioritizedKeys = [\n 'displayname',\n 'accessionversion',\n 'accession',\n 'sampleid',\n 'sample_id',\n 'id',\n 'strain',\n 'isolate',\n 'submissionid',\n 'name',\n ];\n\n for (const col of columns) {\n const colDict = recordsByColumn[col];\n if (!colDict) {\n continue;\n }\n\n const count = matchCounts[col];\n\n if (count > maxMatches) {\n maxMatches = count;\n bestColumn = col;\n } else if (count === maxMatches && count > 0 && bestColumn) {\n // Tie-breaker: prioritize standard ID column names\n const currentPriority = prioritizedKeys.indexOf(bestColumn.toLowerCase());\n const newPriority = prioritizedKeys.indexOf(col.toLowerCase());\n if (newPriority !== -1 && (currentPriority === -1 || newPriority < currentPriority)) {\n bestColumn = col;\n }\n }\n }\n\n if (maxMatches <= 0) {\n // If no matches, default to highest priority known ID column if available\n for (const pKey of prioritizedKeys) {\n const found = columns.find((c) => c.toLowerCase() === pKey);\n if (found) {\n bestColumn = found;\n break;\n }\n }\n if (!bestColumn && columns.length > 0) {\n bestColumn = columns[0];\n }\n }\n\n return { bestColumn, matchCounts };\n };\n\n public static getHighlightedIds(query: string, allAccessions: string[], metadataRecords: MetadataRecord[], matchKeyField: null | string = null): string[] {\n const trimmed = query.trim().toLowerCase();\n if (!trimmed) {\n return [];\n }\n\n const matched = new Set<string>();\n\n // Check accession names / fasta IDs\n allAccessions.forEach((acc) => {\n if (acc.toLowerCase().includes(trimmed)) {\n matched.add(acc);\n }\n });\n\n // Check metadata records\n metadataRecords.forEach((rec) => {\n const acc =\n (matchKeyField && rec[matchKeyField]) ||\n rec.displayName ||\n rec.accessionVersion ||\n rec.accession ||\n rec.id ||\n rec.submissionId ||\n rec.SampleID;\n if (acc) {\n const matchesAnyField = Object.values(rec).some(\n (val) => typeof val === 'string' && val.toLowerCase().includes(trimmed),\n );\n if (matchesAnyField) {\n matched.add(acc);\n }\n }\n });\n\n return Array.from(matched);\n }\n /**\n * Count FASTA sequence ID matches for each metadata column.\n */\n public static getMatchCounts = (\n columns: string[],\n recordsByColumn: Record<string, Record<string, MetadataRecord>>,\n fastaIds: string[],\n ): Record<string, number> => {\n const matchCounts: Record<string, number> = {};\n\n if (!columns || !recordsByColumn || !fastaIds || fastaIds.length === 0) {\n return matchCounts;\n }\n\n for (const col of columns) {\n const colDict = recordsByColumn[col];\n if (!colDict) {\n continue;\n }\n\n let count = 0;\n for (const id of fastaIds) {\n if (colDict[id]) {\n count++;\n }\n }\n matchCounts[col] = count;\n }\n\n return matchCounts;\n };\n\n /**\n * Retrieve metadata record for a given sample/node name with fallback strategies.\n */\n public static readonly getRecordForSample = (\n metadata: MetadataParseResult | null | undefined,\n sampleId: string,\n matchKeyField?: null | string,\n ): MetadataRecord | undefined => {\n if (!metadata || !sampleId) {\n return undefined;\n }\n\n // 1. If matchKeyField is specified and exists, check it first\n if (matchKeyField && metadata.recordsByColumn?.[matchKeyField]) {\n const rec = metadata.recordsByColumn[matchKeyField][sampleId];\n if (rec) {\n return rec;\n }\n }\n\n // 2. Check displayName\n if (metadata.recordsByColumn?.['displayName']?.[sampleId]) {\n return metadata.recordsByColumn['displayName'][sampleId];\n }\n\n // 3. Check accessionVersion\n if (metadata.recordsByColumn?.['accessionVersion']?.[sampleId]) {\n return metadata.recordsByColumn['accessionVersion'][sampleId];\n }\n\n // 4. Check accession maps\n if (metadata.recordsByAccession[sampleId]) {\n return metadata.recordsByAccession[sampleId];\n }\n if (metadata.recordsBySampleId[sampleId]) {\n return metadata.recordsBySampleId[sampleId];\n }\n\n // 5. Broad search through all indexed columns\n if (metadata.recordsByColumn) {\n for (const col of Object.keys(metadata.recordsByColumn)) {\n const match = metadata.recordsByColumn[col][sampleId];\n if (match) {\n return match;\n }\n }\n }\n\n return undefined;\n };\n\n public static parseTsv = (content: string): MetadataParseResult => {\n if (!content?.trim()) {\n return {\n columns: [],\n error: 'Empty metadata content provided.',\n records: [],\n recordsByAccession: {},\n recordsByColumn: {},\n recordsBySampleId: {},\n };\n }\n\n const lines = content.trim().split('\\n');\n if (lines.length < 2) {\n return {\n columns: [],\n error: 'Metadata file must contain at least a header row and one data row.',\n records: [],\n recordsByAccession: {},\n recordsByColumn: {},\n recordsBySampleId: {},\n };\n }\n\n // Parse header row\n const headerLine = lines[0].trim();\n let delimiter = '\\t';\n if (!headerLine.includes('\\t') && headerLine.includes(',')) {\n delimiter = ',';\n }\n const columns = headerLine\n .split(delimiter)\n .map((col) => col.trim().replace(/^[\"']|[\"']$/g, ''))\n .filter((c) => c.length > 0);\n\n const records: MetadataRecord[] = [];\n const recordsByAccession: Record<string, MetadataRecord> = {};\n const recordsBySampleId: Record<string, MetadataRecord> = {};\n const recordsByColumn: Record<string, Record<string, MetadataRecord>> = {};\n\n columns.forEach((col) => {\n recordsByColumn[col] = {};\n });\n\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i].trim();\n if (!line) {\n continue;\n }\n\n const values = line.split(delimiter).map((val) => val.trim().replace(/^[\"']|[\"']$/g, ''));\n const record: MetadataRecord = {};\n\n columns.forEach((col, colIdx) => {\n const val = values[colIdx] ?? '';\n record[col] = val;\n if (val) {\n recordsByColumn[col][val] = record;\n }\n });\n\n records.push(record);\n\n // Index by potential ID keys\n const accessionKey =\n record.displayName ||\n record.accessionVersion ||\n record.accession ||\n record.id ||\n record.submissionId ||\n record.strain ||\n record.isolate ||\n record.name;\n\n if (accessionKey) {\n recordsByAccession[accessionKey] = record;\n }\n\n const sampleIdKey =\n record.SampleID ||\n record.sampleId ||\n record.sample_id ||\n record.specimenCollectorSampleId ||\n record.id;\n\n if (sampleIdKey) {\n recordsBySampleId[sampleIdKey] = record;\n }\n }\n\n return {\n columns,\n records,\n recordsByAccession,\n recordsByColumn,\n recordsBySampleId,\n };\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/utils/UrlUtil/UrlUtil.ts"],"sourcesContent":["import { WindowService } from '@gen-epix/ui-core/classes/services/WindowService';\n\nexport class UrlUtil {\n public static clearSearchParams(): void {\n const url = new URL(WindowService.getInstance().window.location.href);\n url.search = '';\n WindowService.getInstance().window.history.replaceState(null, '', url.toString());\n }\n\n public static fastaLabelFromUrl(url: string): string {\n try {\n const urlObject = new URL(url);\n const segment = urlObject.pathname.split('/').filter(Boolean).pop();\n if (segment) {\n return decodeURIComponent(segment);\n }\n return `${urlObject.hostname}.fasta`;\n } catch {\n return 'remote-alignment.fasta';\n }\n }\n\n public static validateFastaUrl(url: string): string {\n let urlObject: URL;\n try {\n urlObject = new URL(url.trim());\n } catch {\n throw new Error('Enter a valid FASTA URL.');\n }\n if (urlObject.protocol !== 'http:' && urlObject.protocol !== 'https:') {\n throw new Error('FASTA URLs must use HTTP or HTTPS.');\n }\n return urlObject.href;\n }\n}\n"],"mappings":";;AAEA,IAAa,IAAb,MAAqB;CACnB,OAAc,oBAA0B;EACtC,IAAM,IAAM,IAAI,IAAI,EAAc,YAAY,CAAC,CAAC,OAAO,SAAS,IAAI;EAEpE,AADA,EAAI,SAAS,IACb,EAAc,YAAY,CAAC,CAAC,OAAO,QAAQ,aAAa,MAAM,IAAI,EAAI,SAAS,CAAC;CAClF;CAEA,OAAc,kBAAkB,GAAqB;EACnD,IAAI;GACF,IAAM,IAAY,IAAI,IAAI,CAAG,GACvB,IAAU,EAAU,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI;GAIlE,OAHI,IACK,mBAAmB,CAAO,IAE5B,GAAG,EAAU,SAAS;EAC/B,QAAQ;GACN,OAAO;EACT;CACF;CAEA,OAAc,iBAAiB,GAAqB;EAClD,IAAI;EACJ,IAAI;GACF,IAAY,IAAI,IAAI,EAAI,KAAK,CAAC;EAChC,QAAQ;GACN,MAAU,MAAM,0BAA0B;EAC5C;EACA,IAAI,EAAU,aAAa,WAAW,EAAU,aAAa,UAC3D,MAAU,MAAM,oCAAoC;EAEtD,OAAO,EAAU;CACnB;AACF"}
1
+ {"version":3,"file":"index.js","names":["UrlUtil","url","WindowService","urlObject","segment"],"sources":["../../../src/utils/UrlUtil/UrlUtil.ts"],"sourcesContent":["import { WindowService } from '@gen-epix/ui-core/classes/services/WindowService';\n\nexport class UrlUtil {\n public static clearSearchParams(): void {\n const url = new URL(WindowService.getInstance().window.location.href);\n url.search = '';\n WindowService.getInstance().window.history.replaceState(null, '', url.toString());\n }\n\n public static fastaLabelFromUrl(url: string): string {\n try {\n const urlObject = new URL(url);\n const segment = urlObject.pathname.split('/').filter(Boolean).pop();\n if (segment) {\n return decodeURIComponent(segment);\n }\n return `${urlObject.hostname}.fasta`;\n } catch {\n return 'remote-alignment.fasta';\n }\n }\n\n public static validateFastaUrl(url: string): string {\n let urlObject: URL;\n try {\n urlObject = new URL(url.trim());\n } catch {\n throw new Error('Enter a valid FASTA URL.');\n }\n if (urlObject.protocol !== 'http:' && urlObject.protocol !== 'https:') {\n throw new Error('FASTA URLs must use HTTP or HTTPS.');\n }\n return urlObject.href;\n }\n}\n"],"mappings":";;AAEA,IAAaA,IAAb,MAAqB;CACnB,OAAc,oBAA0B;EACtC,IAAMC,IAAM,IAAI,IAAIC,EAAc,YAAY,CAAC,CAAC,OAAO,SAAS,IAAI;EAEpE,AADA,EAAI,SAAS,IACbA,EAAc,YAAY,CAAC,CAAC,OAAO,QAAQ,aAAa,MAAM,IAAID,EAAI,SAAS,CAAC;CAClF;CAEA,OAAc,kBAAkB,GAAqB;EACnD,IAAI;GACF,IAAME,IAAY,IAAI,IAAIF,CAAG,GACvBG,IAAUD,EAAU,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI;GAIlE,OAHIC,IACK,mBAAmBA,CAAO,IAE5B,GAAGD,EAAU,SAAS;EAC/B,QAAQ;GACN,OAAO;EACT;CACF;CAEA,OAAc,iBAAiB,GAAqB;EAClD,IAAIA;EACJ,IAAI;GACF,IAAY,IAAI,IAAIF,EAAI,KAAK,CAAC;EAChC,QAAQ;GACN,MAAU,MAAM,0BAA0B;EAC5C;EACA,IAAIE,EAAU,aAAa,WAAWA,EAAU,aAAa,UAC3D,MAAU,MAAM,oCAAoC;EAEtD,OAAOA,EAAU;CACnB;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivm-loculus-tools/common-frontend",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "license": "EUPL-1.2",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,9 +11,9 @@
11
11
  "@emotion/cache": "*",
12
12
  "@emotion/react": "*",
13
13
  "@emotion/styled": "*",
14
- "@gen-epix/ui-core-components": "^4.3.1",
15
- "@gen-epix/ui-core-form": "^4.3.1",
16
- "@gen-epix/ui-core": "^3.8.1",
14
+ "@gen-epix/ui-core-components": "^4.4.0",
15
+ "@gen-epix/ui-core-form": "^4.4.0",
16
+ "@gen-epix/ui-core": "^3.9.0",
17
17
  "@mui/icons-material": "*",
18
18
  "@mui/material": "*",
19
19
  "@mui/styled-engine": "*",
@@ -33,31 +33,31 @@
33
33
  "@emotion/cache": "11.14.0",
34
34
  "@emotion/react": "11.14.0",
35
35
  "@emotion/styled": "11.14.1",
36
- "@gen-epix/eslint-config": "0.7.0",
37
- "@gen-epix/ui-core-components": "4.3.1",
38
- "@gen-epix/ui-core-form": "4.3.1",
39
- "@gen-epix/ui-core": "3.8.1",
40
- "@mui/icons-material": "9.4.0",
41
- "@mui/material": "9.4.0",
42
- "@mui/styled-engine": "9.4.0",
43
- "@mui/system": "9.4.0",
44
- "@mui/utils": "9.4.0",
45
- "@types/lodash": "4.17.25",
46
- "@types/node": "26.6.2",
36
+ "@gen-epix/eslint-config": "0.8.0",
37
+ "@gen-epix/ui-core-components": "4.4.0",
38
+ "@gen-epix/ui-core-form": "4.4.0",
39
+ "@gen-epix/ui-core": "3.9.0",
40
+ "@mui/icons-material": "9.5.0",
41
+ "@mui/material": "9.5.0",
42
+ "@mui/styled-engine": "9.5.0",
43
+ "@mui/system": "9.5.0",
44
+ "@mui/utils": "9.5.0",
45
+ "@types/lodash": "4.17.26",
46
+ "@types/node": "26.6.5",
47
47
  "@types/react-dom": "19.3.0",
48
48
  "@types/react": "19.3.0",
49
- "@vitejs/plugin-react": "6.1.1",
50
- "eslint": "10.11.0",
51
- "i18next": "26.4.2",
49
+ "@vitejs/plugin-react": "6.1.2",
50
+ "eslint": "10.12.0",
51
+ "i18next": "26.4.4",
52
52
  "react-dom": "19.3.0",
53
53
  "react-error-boundary": "6.1.6",
54
- "react-hook-form": "7.88.0",
55
- "react-i18next": "17.0.15",
54
+ "react-hook-form": "7.90.0",
55
+ "react-i18next": "17.0.16",
56
56
  "react": "19.3.0",
57
57
  "typescript": "6.0.3",
58
- "vite-plugin-dts": "5.1.1",
58
+ "vite-plugin-dts": "5.1.2",
59
59
  "vite-plugin-static-copy": "4.1.1",
60
- "vite": "8.3.0"
60
+ "vite": "8.3.4"
61
61
  },
62
62
  "files": [
63
63
  "dist"