From 405ca45214857df91305a8ce0f01a49d6108106f Mon Sep 17 00:00:00 2001 From: rrgoetz Date: Tue, 14 Jan 2025 06:49:23 -1000 Subject: [PATCH 1/7] Bugfix allow for paths in Headers --- src/utilities/codemirror/satf/satf-sasf.grammar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities/codemirror/satf/satf-sasf.grammar b/src/utilities/codemirror/satf/satf-sasf.grammar index dea9ecfd47..e40037d42f 100644 --- a/src/utilities/codemirror/satf/satf-sasf.grammar +++ b/src/utilities/codemirror/satf/satf-sasf.grammar @@ -26,7 +26,7 @@ HeaderPairs { HeaderPair* } HeaderPair {Key"="Value ";" newLine} Key { (identifier | ":")* } - Value { headerValue+ } + Value { (headerValue | "/")+ } SfduHeader { headerMarker newLine HeaderPairs headerMarker} Start { "$$"identifier identifier* newLine} From f5db855fd23d49b2b7db88d7d3e84b1e18c7a238 Mon Sep 17 00:00:00 2001 From: rrgoetz Date: Tue, 14 Jan 2025 06:51:54 -1000 Subject: [PATCH 2/7] Library Sequence Modal This modal allows you to select a parcel and import a library sequence file --- .../modals/LibrarySequenceModal.svelte | 67 +++++++++++++++++++ src/utilities/modal.ts | 34 ++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/components/modals/LibrarySequenceModal.svelte diff --git a/src/components/modals/LibrarySequenceModal.svelte b/src/components/modals/LibrarySequenceModal.svelte new file mode 100644 index 0000000000..9f2b463522 --- /dev/null +++ b/src/components/modals/LibrarySequenceModal.svelte @@ -0,0 +1,67 @@ + + + + + + + + {modalTitle} + + +
Parcel (required)
+ +
+ + +
+
+ + + + + +
diff --git a/src/utilities/modal.ts b/src/utilities/modal.ts index 34cb179fb6..ed04afcff1 100644 --- a/src/utilities/modal.ts +++ b/src/utilities/modal.ts @@ -12,6 +12,7 @@ import DeleteExternalEventSourceTypeModal from '../components/modals/DeleteExter import DeleteExternalSourceModal from '../components/modals/DeleteExternalSourceModal.svelte'; import EditViewModal from '../components/modals/EditViewModal.svelte'; import ExpansionSequenceModal from '../components/modals/ExpansionSequenceModal.svelte'; +import LibrarySequenceModal from '../components/modals/LibrarySequenceModal.svelte'; import ManageGroupsAndTypesModal from '../components/modals/ManageGroupsAndTypesModal.svelte'; import ManagePlanConstraintsModal from '../components/modals/ManagePlanConstraintsModal.svelte'; import ManagePlanDerivationGroupsModal from '../components/modals/ManagePlanDerivationGroupsModal.svelte'; @@ -556,6 +557,39 @@ export async function showWorkspaceModal( }); } +export async function showLibrarySequenceModel(): Promise> { + return new Promise(resolve => { + if (browser) { + const target: ModalElement | null = document.querySelector('#svelte-modal'); + + if (target) { + const workspaceModal = new LibrarySequenceModal({ + target, + }); + target.resolve = resolve; + + workspaceModal.$on('close', () => { + target.replaceChildren(); + target.resolve = null; + resolve({ confirm: false }); + workspaceModal.$destroy(); + }); + + workspaceModal.$on('save', (e: CustomEvent<{ library: FileList; parcel: number }>) => { + const library = e.detail.library[0]; + const parcel = e.detail.parcel; + target.replaceChildren(); + target.resolve = null; + resolve({ confirm: true, value: { libraryFile: library, parcel } }); + workspaceModal.$destroy(); + }); + } + } else { + resolve({ confirm: false }); + } + }); +} + /** * Shows a CreatePlanBranchModal with the supplied arguments. */ From b86da46b8105f0e108e44ccad8b1b9d06c20a0e4 Mon Sep 17 00:00:00 2001 From: rrgoetz Date: Tue, 14 Jan 2025 08:07:11 -1000 Subject: [PATCH 3/7] Hook up the library modal to workspaces --- src/components/sequencing/Sequences.svelte | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/components/sequencing/Sequences.svelte b/src/components/sequencing/Sequences.svelte index 7a4651e436..958392f9bb 100644 --- a/src/components/sequencing/Sequences.svelte +++ b/src/components/sequencing/Sequences.svelte @@ -9,9 +9,13 @@ import { parcels, userSequences, userSequencesColumns, workspaces } from '../../stores/sequencing'; import type { User } from '../../types/app'; import type { Parcel, UserSequence, Workspace } from '../../types/sequencing'; + import { satfToSequence } from '../../utilities/codemirror/satf/satf-sasf-utils'; + import effects from '../../utilities/effects'; import { getSearchParameterNumber, setQueryParam } from '../../utilities/generic'; + import { showLibrarySequenceModel } from '../../utilities/modal'; import { permissionHandler } from '../../utilities/permissionHandler'; import { featurePermissions } from '../../utilities/permissions'; + import { showFailureToast } from '../../utilities/toast'; import Input from '../form/Input.svelte'; import CssGrid from '../ui/CssGrid.svelte'; import CssGridGutter from '../ui/CssGridGutter.svelte'; @@ -59,6 +63,41 @@ const workspaceId = getSearchParameterNumber(SearchParameters.WORKSPACE_ID); goto(`${base}/sequencing/new${workspaceId ? `?${SearchParameters.WORKSPACE_ID}=${workspaceId}` : ''}`); } + + async function importLibarySequences(): Promise { + const workspaceId = getSearchParameterNumber(SearchParameters.WORKSPACE_ID); + if (workspaceId === null) { + console.log("Workspace doesn't exist"); + showFailureToast("Library Import: Workspace doesn't exist"); + return; + } + const { confirm, value } = await showLibrarySequenceModel(); + + if (confirm && value) { + const fileName = await value.libraryFile.name; + const type = fileName.slice(fileName.lastIndexOf('.') + 1); + if (type !== 'satf') { + console.log(`Unsupported file type ${type}`); + showFailureToast('Library Import: Unsupported file type'); + return; + } + const contents = await value.libraryFile.text(); + const parcel = await value.parcel; + const sequences = (await satfToSequence(contents)).sequences; + sequences.forEach(async seqN => { + await effects.createUserSequence( + { + definition: seqN.sequence, + name: seqN.name, + parcel_id: parcel, + seq_json: '', + workspace_id: workspaceId, + }, + user, + ); + }); + } + } @@ -86,6 +125,18 @@ > New Sequence + + From b23818481f74d0d3a6d16ef1d809d2df84301566 Mon Sep 17 00:00:00 2001 From: rrgoetz Date: Tue, 14 Jan 2025 08:09:38 -1000 Subject: [PATCH 4/7] Fixed incorrect linting errors when multiple ranges are defined. The linter incorrectly reported errors for values within a valid range when multiple ranges were defined. For example, if the ranges were 5-10 and 11-20, the linter would erroneously flag the value 8 as invalid because it falls outside the 11-20 range, even though it is valid within the 5-10 range. --- src/utilities/sequence-editor/sequence-linter.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/utilities/sequence-editor/sequence-linter.ts b/src/utilities/sequence-editor/sequence-linter.ts index 653e849fa4..7ce858c969 100644 --- a/src/utilities/sequence-editor/sequence-linter.ts +++ b/src/utilities/sequence-editor/sequence-linter.ts @@ -566,16 +566,24 @@ function validateActivateLoad( } else { value = parseInt(num); } - parameter.allowable_ranges?.forEach(range => { - if (value < range.min || value > range.max) { + + if (parameter.allowable_ranges) { + const invalidRanges = parameter.allowable_ranges.filter(range => { + return value < range.min || value > range.max; + }); + if (invalidRanges.length === parameter.allowable_ranges.length) { diagnostics.push({ from: arg.from, - message: `Value must be between ${range.min} and ${range.max}`, + message: `Value must be between ${parameter.allowable_ranges + .map(range => { + return `[${range.min} and ${range.max}]`; + }) + .join(' or ')}`, severity: 'error', to: arg.to, }); } - }); + } if (parameter.type === 'UINT') { if (value < 0) { From f26f2e56938a665d1199dcd68ff28aeda886b5cc Mon Sep 17 00:00:00 2001 From: rrgoetz Date: Tue, 14 Jan 2025 08:13:32 -1000 Subject: [PATCH 5/7] Improved autocomplete functionality by allowing completion even with invalid variable types. Allow autocomplete to succeed and show the ERROR parameter value. The user will see this error in the editor and manually have to fix it. --- src/utilities/codemirror/codemirror-utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utilities/codemirror/codemirror-utils.ts b/src/utilities/codemirror/codemirror-utils.ts index 5dc316e65f..daa480b063 100644 --- a/src/utilities/codemirror/codemirror-utils.ts +++ b/src/utilities/codemirror/codemirror-utils.ts @@ -120,10 +120,11 @@ export function getDefaultVariableArgs(parameters: VariableDeclaration[]): strin return parameter.allowable_values && parameter.allowable_values.length > 0 ? `"${parameter.allowable_values[0]}"` : parameter.enum_name - ? `${parameter.enum_name}` + ? `"${parameter.enum_name}"` : 'UNKNOWN'; default: - throw Error(`unknown argument type ${parameter.type}`); + console.log(`Unknown argument type ${parameter.type}`); + return `ERROR:"${parameter.name}"`; } }) as string[]; } From 3bcb54445c60091bee55b3cbad9fe061abbe89a0 Mon Sep 17 00:00:00 2001 From: rrgoetz Date: Tue, 14 Jan 2025 12:02:19 -1000 Subject: [PATCH 6/7] Fixed linter error for commands without arguments. The linter will complain if there is a command with no arguments even though the command itself doesn't have any argument defined in the command dictionary. --- src/utilities/sequence-editor/sequence-linter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities/sequence-editor/sequence-linter.ts b/src/utilities/sequence-editor/sequence-linter.ts index 7ce858c969..1324f9acf7 100644 --- a/src/utilities/sequence-editor/sequence-linter.ts +++ b/src/utilities/sequence-editor/sequence-linter.ts @@ -1245,7 +1245,7 @@ function validateCommandStructure( addDefault: (view: any) => any, ): Diagnostic | undefined { if (arguments.length > 0) { - if (!argsNode || argsNode.length === 0) { + if (!argsNode || (argsNode.length === 0 && exactArgSize > 0)) { return { actions: [], from: stemNode.from, From 98330e2f408ba5781b49bea21f50a7f58b238e2c Mon Sep 17 00:00:00 2001 From: rrgoetz Date: Tue, 14 Jan 2025 12:03:59 -1000 Subject: [PATCH 7/7] Fix @MODEL generation. There shouldn't be any parenthesis and commas in the SeqN generation --- .../codemirror/satf/satf-sasf-utils.test.ts | 12 ++++++------ src/utilities/codemirror/satf/satf-sasf-utils.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utilities/codemirror/satf/satf-sasf-utils.test.ts b/src/utilities/codemirror/satf/satf-sasf-utils.test.ts index 93a1f3cf5e..02da771e8d 100644 --- a/src/utilities/codemirror/satf/satf-sasf-utils.test.ts +++ b/src/utilities/codemirror/satf/satf-sasf-utils.test.ts @@ -91,9 +91,9 @@ describe('satfToSequence', () => { expect(result.sequences[0].name).toStrictEqual('test'); expect(result.sequences[0].sequence).toStrictEqual(`## test R00:01:00 01VV param6 10 false "abc" # This command turns, to correct position. -@MODEL(x,1,"00:00:00") -@MODEL(z,1.1,"00:00:00") -@MODEL(y,"abc","00:00:00")`); +@MODEL "x" 1 "00:00:00" +@MODEL "z" 1.1 "00:00:00" +@MODEL "y" "abc" "00:00:00"`); }); it('should return multiple sequence with models', async () => { @@ -127,9 +127,9 @@ R00:01:00 01VV param6 10 false "abc" # This command turns, to correct position. expect(result.sequences[0].name).toStrictEqual('test'); expect(result.sequences[0].sequence).toStrictEqual(`## test R00:01:00 01VV param6 10 false "abc" # This command turns, to correct position. -@MODEL(x,1,"00:00:00") -@MODEL(z,1.1,"00:00:00") -@MODEL(y,"abc","00:00:00")`); +@MODEL "x" 1 "00:00:00" +@MODEL "z" 1.1 "00:00:00" +@MODEL "y" "abc" "00:00:00"`); }); }); diff --git a/src/utilities/codemirror/satf/satf-sasf-utils.ts b/src/utilities/codemirror/satf/satf-sasf-utils.ts index 6018d8ebec..582e2c50ad 100644 --- a/src/utilities/codemirror/satf/satf-sasf-utils.ts +++ b/src/utilities/codemirror/satf/satf-sasf-utils.ts @@ -881,7 +881,7 @@ function parseModel(modelNode: SyntaxNode | null, text: string): string { if (!keyNode || !valueNode) { return null; } - return `@MODEL(${text.slice(keyNode.from, keyNode.to)},${text.slice(valueNode.from, valueNode.to)},"00:00:00")`; + return `@MODEL "${text.slice(keyNode.from, keyNode.to)}" ${text.slice(valueNode.from, valueNode.to)} "00:00:00"`; }) .filter(model => model !== null) .join('\n');