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/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 + + 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[]; } 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'); 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} 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. */ diff --git a/src/utilities/sequence-editor/sequence-linter.ts b/src/utilities/sequence-editor/sequence-linter.ts index 653e849fa4..1324f9acf7 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) { @@ -1237,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,