Skip to content

Commit ebfaa3a

Browse files
goetzrrGitduranb
andauthored
Import SATF Library Sequence (#1592)
* Bugfix allow for paths in Headers * Library Sequence Modal This modal allows you to select a parcel and import a library sequence file * Hook up the library modal to workspaces * 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. * 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. * Fix @model generation. There shouldn't be any parenthesis and commas in the SeqN generation * fix buttons wrapping --------- Co-authored-by: bduran <[email protected]>
1 parent bfbef53 commit ebfaa3a

10 files changed

+202
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<svelte:options immutable={true} />
2+
3+
<script lang="ts">
4+
import { createEventDispatcher } from 'svelte';
5+
import { parcels } from '../../stores/sequencing';
6+
import Modal from './Modal.svelte';
7+
import ModalContent from './ModalContent.svelte';
8+
import ModalFooter from './ModalFooter.svelte';
9+
import ModalHeader from './ModalHeader.svelte';
10+
11+
export let height: number = 200;
12+
export let width: number = 380;
13+
14+
const dispatch = createEventDispatcher<{
15+
close: void;
16+
save: { library: FileList; parcel: number };
17+
}>();
18+
19+
let saveButtonDisabled: boolean = true;
20+
let modalTitle: string;
21+
let libraryName: FileList;
22+
let parcelId: number;
23+
24+
$: saveButtonDisabled = parcelId === null || libraryName === undefined;
25+
$: modalTitle = 'Import Library';
26+
27+
function save() {
28+
if (!saveButtonDisabled) {
29+
dispatch('save', { library: libraryName, parcel: parcelId });
30+
}
31+
}
32+
33+
function onKeydown(event: KeyboardEvent) {
34+
const { key } = event;
35+
if (key === 'Enter') {
36+
event.preventDefault();
37+
save();
38+
}
39+
}
40+
</script>
41+
42+
<svelte:window on:keydown={onKeydown} />
43+
44+
<Modal {height} {width}>
45+
<ModalHeader on:close>{modalTitle}</ModalHeader>
46+
47+
<ModalContent>
48+
<div class="st-typography-body">Parcel (required)</div>
49+
<select bind:value={parcelId} class="st-select w-100" name="parcel">
50+
<option value={null} />
51+
{#each $parcels as parcel}
52+
<option value={parcel.id}>
53+
{parcel.name}
54+
</option>
55+
{/each}
56+
</select>
57+
<fieldset>
58+
<label for="name">Imported Library</label>
59+
<input bind:files={libraryName} class="w-100" name="libraryFile" type="file" accept=".satf" />
60+
</fieldset>
61+
</ModalContent>
62+
63+
<ModalFooter>
64+
<button class="st-button secondary" on:click={() => dispatch('close')}> Cancel </button>
65+
<button class="st-button" disabled={saveButtonDisabled} on:click={save}> Import </button>
66+
</ModalFooter>
67+
</Modal>

src/components/sequencing/Sequences.svelte

+44
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import { parcels, userSequences, userSequencesColumns, workspaces } from '../../stores/sequencing';
1010
import type { User } from '../../types/app';
1111
import type { Parcel, UserSequence, Workspace } from '../../types/sequencing';
12+
import { satfToSequence } from '../../utilities/codemirror/satf/satf-sasf-utils';
13+
import effects from '../../utilities/effects';
1214
import { getSearchParameterNumber, setQueryParam } from '../../utilities/generic';
1315
import { permissionHandler } from '../../utilities/permissionHandler';
1416
import { featurePermissions } from '../../utilities/permissions';
@@ -59,6 +61,28 @@
5961
const workspaceId = getSearchParameterNumber(SearchParameters.WORKSPACE_ID);
6062
goto(`${base}/sequencing/new${workspaceId ? `?${SearchParameters.WORKSPACE_ID}=${workspaceId}` : ''}`);
6163
}
64+
65+
async function importLibrary(): Promise<void> {
66+
const library = await effects.importLibrarySequences(workspaceId);
67+
if (!library) {
68+
return;
69+
}
70+
71+
const parcel = library.parcel;
72+
const sequences = (await satfToSequence(library.fileContents)).sequences;
73+
sequences.forEach(async seqN => {
74+
await effects.createUserSequence(
75+
{
76+
definition: seqN.sequence,
77+
name: seqN.name,
78+
parcel_id: parcel,
79+
seq_json: '',
80+
workspace_id: workspaceId !== null ? workspaceId : -1,
81+
},
82+
user,
83+
);
84+
});
85+
}
6286
</script>
6387

6488
<CssGrid bind:columns={$userSequencesColumns}>
@@ -86,6 +110,18 @@
86110
>
87111
New Sequence
88112
</button>
113+
114+
<button
115+
class="st-button secondary ellipsis"
116+
use:permissionHandler={{
117+
hasPermission: featurePermissions.sequences.canCreate(user),
118+
permissionError: 'You do not have permission to upload library sequences',
119+
}}
120+
disabled={workspace === undefined}
121+
on:click|stopPropagation={importLibrary}
122+
>
123+
Import Library
124+
</button>
89125
</div>
90126
</svelte:fragment>
91127

@@ -108,3 +144,11 @@
108144
{user}
109145
/>
110146
</CssGrid>
147+
148+
<style>
149+
.right {
150+
column-gap: 5px;
151+
display: flex;
152+
flex-wrap: nowrap;
153+
}
154+
</style>

src/utilities/codemirror/codemirror-utils.test.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,12 @@ describe('getDefaultVariableArgs', () => {
356356
{ allowable_ranges: [{ min: 5 }], type: 'INT' },
357357
{ allowable_ranges: [{ min: 7 }], type: 'UINT' },
358358
{ allowable_values: ['VALUE1'], enum_name: 'ExampleEnum', type: 'ENUM' },
359+
{ enum_name: 'ExampleEnum2', type: 'ENUM' },
359360
{ type: 'INT' },
361+
{ name: 'hexValue', type: 'HEX' },
360362
] as VariableDeclaration[];
361363
it('should return default values for different types', () => {
362364
const result = getDefaultVariableArgs(mockParameters);
363-
expect(result).toEqual(['"exampleString"', 1.2, 5, 7, '"VALUE1"', 0]);
365+
expect(result).toEqual(['"exampleString"', 1.2, 5, 7, '"VALUE1"', '"ExampleEnum2"', 0, 'ERROR:"hexValue"']);
364366
});
365367
});

src/utilities/codemirror/codemirror-utils.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,10 @@ export function getDefaultVariableArgs(parameters: VariableDeclaration[]): strin
120120
return parameter.allowable_values && parameter.allowable_values.length > 0
121121
? `"${parameter.allowable_values[0]}"`
122122
: parameter.enum_name
123-
? `${parameter.enum_name}`
123+
? `"${parameter.enum_name}"`
124124
: 'UNKNOWN';
125125
default:
126-
throw Error(`unknown argument type ${parameter.type}`);
126+
return `ERROR:"${parameter.name}"`;
127127
}
128128
}) as string[];
129129
}

src/utilities/codemirror/satf/satf-sasf-utils.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ describe('satfToSequence', () => {
9191
expect(result.sequences[0].name).toStrictEqual('test');
9292
expect(result.sequences[0].sequence).toStrictEqual(`## test
9393
R00:01:00 01VV param6 10 false "abc" # This command turns, to correct position.
94-
@MODEL(x,1,"00:00:00")
95-
@MODEL(z,1.1,"00:00:00")
96-
@MODEL(y,"abc","00:00:00")`);
94+
@MODEL "x" 1 "00:00:00"
95+
@MODEL "z" 1.1 "00:00:00"
96+
@MODEL "y" "abc" "00:00:00"`);
9797
});
9898

9999
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.
127127
expect(result.sequences[0].name).toStrictEqual('test');
128128
expect(result.sequences[0].sequence).toStrictEqual(`## test
129129
R00:01:00 01VV param6 10 false "abc" # This command turns, to correct position.
130-
@MODEL(x,1,"00:00:00")
131-
@MODEL(z,1.1,"00:00:00")
132-
@MODEL(y,"abc","00:00:00")`);
130+
@MODEL "x" 1 "00:00:00"
131+
@MODEL "z" 1.1 "00:00:00"
132+
@MODEL "y" "abc" "00:00:00"`);
133133
});
134134
});
135135

src/utilities/codemirror/satf/satf-sasf-utils.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,15 @@ export function generateSatfVariables(
475475
return (
476476
`\t${variable.name}` +
477477
`(\n\t\tTYPE,${variable.type}${variable.enum_name ? `\n\tENUM,${variable.enum_name}` : ''}` +
478-
`${variable.allowable_ranges ? `\n\t\tRANGES,${variable.allowable_ranges}` : ''}` +
478+
`${
479+
variable.allowable_ranges
480+
? variable.allowable_ranges
481+
.map(range => {
482+
return `\n\t\tRANGES,\\${range.min}...${range.max}\\`;
483+
})
484+
.join(',')
485+
: ''
486+
}` +
479487
`${variable.allowable_values ? `\n\t\tVALUES,${variable.allowable_values}` : ''}` +
480488
`${variable.sc_name ? `\n\t\tSC_NAME,${variable.sc_name}` : ''}\n\t)`
481489
);
@@ -881,7 +889,7 @@ function parseModel(modelNode: SyntaxNode | null, text: string): string {
881889
if (!keyNode || !valueNode) {
882890
return null;
883891
}
884-
return `@MODEL(${text.slice(keyNode.from, keyNode.to)},${text.slice(valueNode.from, valueNode.to)},"00:00:00")`;
892+
return `@MODEL "${text.slice(keyNode.from, keyNode.to)}" ${text.slice(valueNode.from, valueNode.to)} "00:00:00"`;
885893
})
886894
.filter(model => model !== null)
887895
.join('\n');

src/utilities/codemirror/satf/satf-sasf.grammar

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
HeaderPairs { HeaderPair* }
2727
HeaderPair {Key"="Value ";" newLine}
2828
Key { (identifier | ":")* }
29-
Value { headerValue+ }
29+
Value { (headerValue | "/")+ }
3030
SfduHeader { headerMarker newLine HeaderPairs headerMarker}
3131

3232
Start { "$$"identifier identifier* newLine}

src/utilities/effects.ts

+23
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ import {
226226
showDeleteActivitiesModal,
227227
showDeleteExternalSourceModal,
228228
showEditViewModal,
229+
showLibrarySequenceModel,
229230
showManageGroupsAndTypes,
230231
showManagePlanConstraintsModal,
231232
showManagePlanDerivationGroups,
@@ -4791,6 +4792,28 @@ const effects = {
47914792
}
47924793
},
47934794

4795+
async importLibrarySequences(
4796+
workspaceId: number | null,
4797+
): Promise<{ fileContents: string; parcel: number } | undefined> {
4798+
if (workspaceId === null) {
4799+
showFailureToast("Library Import: Workspace doesn't exist");
4800+
return undefined;
4801+
}
4802+
const { confirm, value } = await showLibrarySequenceModel();
4803+
4804+
if (!confirm || !value) {
4805+
return undefined;
4806+
}
4807+
4808+
try {
4809+
const contents = await value.libraryFile.text();
4810+
return { fileContents: contents, parcel: value.parcel };
4811+
} catch (e) {
4812+
showFailureToast('Library Import: Unable to open file');
4813+
return undefined;
4814+
}
4815+
},
4816+
47944817
async importPlan(
47954818
name: string,
47964819
modelId: number,

src/utilities/modal.ts

+34
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import DeleteExternalEventSourceTypeModal from '../components/modals/DeleteExter
1212
import DeleteExternalSourceModal from '../components/modals/DeleteExternalSourceModal.svelte';
1313
import EditViewModal from '../components/modals/EditViewModal.svelte';
1414
import ExpansionSequenceModal from '../components/modals/ExpansionSequenceModal.svelte';
15+
import LibrarySequenceModal from '../components/modals/LibrarySequenceModal.svelte';
1516
import ManageGroupsAndTypesModal from '../components/modals/ManageGroupsAndTypesModal.svelte';
1617
import ManagePlanConstraintsModal from '../components/modals/ManagePlanConstraintsModal.svelte';
1718
import ManagePlanDerivationGroupsModal from '../components/modals/ManagePlanDerivationGroupsModal.svelte';
@@ -556,6 +557,39 @@ export async function showWorkspaceModal(
556557
});
557558
}
558559

560+
export async function showLibrarySequenceModel(): Promise<ModalElementValue<{ libraryFile: File; parcel: number }>> {
561+
return new Promise(resolve => {
562+
if (browser) {
563+
const target: ModalElement | null = document.querySelector('#svelte-modal');
564+
565+
if (target) {
566+
const workspaceModal = new LibrarySequenceModal({
567+
target,
568+
});
569+
target.resolve = resolve;
570+
571+
workspaceModal.$on('close', () => {
572+
target.replaceChildren();
573+
target.resolve = null;
574+
resolve({ confirm: false });
575+
workspaceModal.$destroy();
576+
});
577+
578+
workspaceModal.$on('save', (e: CustomEvent<{ library: FileList; parcel: number }>) => {
579+
const library = e.detail.library[0];
580+
const parcel = e.detail.parcel;
581+
target.replaceChildren();
582+
target.resolve = null;
583+
resolve({ confirm: true, value: { libraryFile: library, parcel } });
584+
workspaceModal.$destroy();
585+
});
586+
}
587+
} else {
588+
resolve({ confirm: false });
589+
}
590+
});
591+
}
592+
559593
/**
560594
* Shows a CreatePlanBranchModal with the supplied arguments.
561595
*/

src/utilities/sequence-editor/sequence-linter.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -567,16 +567,24 @@ function validateActivateLoad(
567567
} else {
568568
value = parseInt(num);
569569
}
570-
parameter.allowable_ranges?.forEach(range => {
571-
if (value < range.min || value > range.max) {
570+
571+
if (parameter.allowable_ranges) {
572+
const invalidRanges = parameter.allowable_ranges.filter(range => {
573+
return value < range.min || value > range.max;
574+
});
575+
if (invalidRanges.length === parameter.allowable_ranges.length) {
572576
diagnostics.push({
573577
from: arg.from,
574-
message: `Value must be between ${range.min} and ${range.max}`,
578+
message: `Value must be between ${parameter.allowable_ranges
579+
.map(range => {
580+
return `[${range.min} and ${range.max}]`;
581+
})
582+
.join(' or ')}`,
575583
severity: 'error',
576584
to: arg.to,
577585
});
578586
}
579-
});
587+
}
580588

581589
if (parameter.type === 'UINT') {
582590
if (value < 0) {

0 commit comments

Comments
 (0)