diff --git a/src/tests/dspf.test.ts b/src/tests/dspf.test.ts index cc5469a..10a2b41 100644 --- a/src/tests/dspf.test.ts +++ b/src/tests/dspf.test.ts @@ -1,5 +1,5 @@ import { expect, describe, it } from "vitest"; -import { DdsLineRange, DisplayFile, FieldInfo } from "../ui/dspf"; +import { Conditional, DdsLineRange, DisplayFile, DisplayFileIndicators, FieldInfo } from "../ui/dspf"; describe('DisplayFile tests', () => { @@ -16,12 +16,14 @@ describe('DisplayFile tests', () => { ` A R GLOBAL `, ` A SLNO(04) `, ` A 1 3'---' `, - ` A R FORM1 `, + ` A R FORM1 `, ` A SLNO(06) `, ` A FLD0101 10A B 3 5 `, ` A 20 DSPATR(PR) `, ` A COLOR(YLW) `, ` A FLD0102 10 B 3 5 `, + ` A R FORM2 `, + ` A 30 SLNO(02) `, ]; it('getRangeForFormat', () => { @@ -78,12 +80,12 @@ describe('DisplayFile tests', () => { { name: "COLOR", value: "BLU", - conditions: [] + conditional: new Conditional() }, { name: "DSPATR", value: "PR", - conditions: [] + conditional: new Conditional(` 31`) } ); @@ -91,10 +93,18 @@ describe('DisplayFile tests', () => { expect(lines.length).toBe(3); expect(lines[0]).toBe(` A 4 10'Some text'`); expect(lines[1]).toBe(` A COLOR(BLU)`); - expect(lines[2]).toBe(` A DSPATR(PR)`); + expect(lines[2]).toBe(` A 31 DSPATR(PR)`); }); + it('getLinesForFormat', () => { + let dds = new DisplayFile(); + dds.parse(dspf1); + let lines = DisplayFile.getLinesForFormat(dds.formats[5]); + expect(lines.length).toBe(2); + expect(lines[1]).toBe(` A 30 SLNO(02)`); + }); + it('No duplicate RecordInfo', () => { let dds = new DisplayFile(); dds.parse(dspf1); @@ -102,4 +112,27 @@ describe('DisplayFile tests', () => { expect(new Set(names).size).toBe(names.length); }); + it('Test for Conditional class', () => { + + let cond = new Conditional(); + cond.push(` N10 11N12`); + + expect(cond.getConditions().length).toBe(1); + expect(cond.getConditions().at(0)?.indicators.length).toBe(3); + + cond.push(`O 20 21`); + + expect(cond.getConditions().length).toBe(2); + expect(cond.getConditions().at(1)?.indicators.length).toBe(2); + + let indicators = new DisplayFileIndicators(); + indicators.set(11, true); + expect(cond.isActive(indicators)).toBeTruthy; + indicators.set(10, true); + expect(cond.isActive(indicators)).toBeFalsy; + indicators.set(20, true); + expect(cond.isActive(indicators)).toBeTruthy; + + }); + }); diff --git a/src/ui/dspf.ts b/src/ui/dspf.ts index 7440563..9328740 100644 --- a/src/ui/dspf.ts +++ b/src/ui/dspf.ts @@ -146,7 +146,7 @@ export class DisplayFile { this.currentField.keywords.push({ name: `DATE`, value: undefined, - conditions: [] + conditional: new Conditional() }); break; case `T`: //Time @@ -155,7 +155,7 @@ export class DisplayFile { this.currentField.keywords.push({ name: `TIME`, value: undefined, - conditions: [] + conditional: new Conditional() }); break; default: @@ -163,9 +163,7 @@ export class DisplayFile { break; } - this.currentField.conditions.push( - ...DisplayFile.parseConditionals(conditionals) - ); + this.currentField.conditional.push(conditionals); } this.HandleKeywords(keywords, conditionals); } @@ -178,9 +176,7 @@ export class DisplayFile { this.currentField.length = this.currentField.value.length; this.currentField.displayType = `const`; - this.currentField.conditions.push( - ...DisplayFile.parseConditionals(conditionals) - ); + this.currentField.conditional.push(conditionals); } } this.HandleKeywords(keywords, conditionals); @@ -227,42 +223,11 @@ export class DisplayFile { } - static parseConditionals(conditionColumns: string): Conditional[] { - if (conditionColumns.trim() === "") {return [];} - - /** @type {Conditional[]} */ - let conditionals = []; - - //TODO: something with condition - //const condition = conditionColumns.substring(0, 1); //A (and) or O (or) - - let current = ""; - let negate = false; - let indicator = 0; - - let cIndex = 1; - - while (cIndex <= 7) { - current = conditionColumns.substring(cIndex, cIndex + 3); - - if (current.trim() !== "") { - negate = (conditionColumns.substring(cIndex, cIndex + 1) === "N"); - indicator = Number(conditionColumns.substring(cIndex + 1, cIndex + 3)); - - conditionals.push({indicator, negate}); - } - - cIndex += 3; - } - - return conditionals; - } - static parseKeywords(keywordStrings: string[], conditionalStrings?: { [line: number]: string }) { - let result: { value: string, keywords: Keyword[], conditions: Conditional[] } = { + let result: { value: string, keywords: Keyword[], conditional: Conditional } = { value: ``, keywords: [], - conditions: [] + conditional: new Conditional() }; const newLineMark = `~`; @@ -330,7 +295,7 @@ export class DisplayFile { result.keywords.push({ name: word.toUpperCase(), value: innerValue.length > 0 ? innerValue : undefined, - conditions: conditionals ? DisplayFile.parseConditionals(conditionals) : [] + conditional: new Conditional(conditionals) }); word = ``; @@ -381,10 +346,7 @@ export class DisplayFile { } for (const keyword of field.keywords) { - // TODO: support conditions - newLines.push( - ` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`, - ); + newLines.push(...keyword.conditional.getLinesWithCondition(` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`)); } return newLines; @@ -436,7 +398,6 @@ export class DisplayFile { return { newLines, range }; } - // TODO: test cases static getLinesForFormat(recordFormat: RecordInfo): string[] { const lines: string[] = []; @@ -445,10 +406,7 @@ export class DisplayFile { } for (const keyword of recordFormat.keywords) { - // TODO: support conditions - lines.push( - ` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`, - ); + lines.push(...keyword.conditional.getLinesWithCondition(` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`)); } return lines; @@ -562,7 +520,7 @@ export class RecordInfo { } } -export interface Keyword { name: string, value?: string, conditions: Conditional[] }; +export interface Keyword { name: string, value?: string, conditional: Conditional }; export type DisplayType = "input" | "output" | "both" | "const" | "hidden"; @@ -575,7 +533,7 @@ export class FieldInfo { public decimals: number = 0; public position: { x: number, y: number } = { x: 0, y: 0 }; public keywordStrings: { keywordLines: string[], conditionalLines: { [lineIndex: number]: string } } = { keywordLines: [], conditionalLines: {} }; - public conditions: Conditional[] = []; + public conditional: Conditional = new Conditional(); public keywords: Keyword[] = []; constructor(public startRange: number, public name?: string) {} @@ -591,7 +549,129 @@ export class FieldInfo { } } -export interface Conditional { - indicator: number, - negate: boolean +export interface Condition { + indicators: Indicator[]; +} + +export interface Indicator { + indicator: number, + negate: boolean +} + +export class Conditional { + private conditions: Condition[] = [{ + indicators: [] + }]; + + constructor(indicatorStr?: string) { + if (indicatorStr !== undefined) { + this.push(indicatorStr); + } + } + + push(indicatorStr: string) { + if (indicatorStr.substring(0, 1) === `O` && this.conditions[this.conditions.length - 1].indicators.length > 0) { + if (this.conditions.length >= 9) { + throw new Error("Too many conditions"); + } + this.conditions.push({indicators: []}); + } + + let cIndex = 1; + let current = ``; + let negate = false; + let indicator = 0; + + while (cIndex <= 7) { + current = indicatorStr.substring(cIndex, cIndex + 3); + + if (current.trim() !== "") { + negate = (indicatorStr.substring(cIndex, cIndex + 1) === "N"); + indicator = Number(indicatorStr.substring(cIndex + 1, cIndex + 3)); + if (indicator !== 0) { + if (this.conditions[this.conditions.length - 1].indicators.length >= 9) { + throw new Error("Too many option indicators specified for one condition"); + } + this.conditions[this.conditions.length - 1].indicators.push({ + indicator: indicator, + negate: negate + }); + } + } + + cIndex += 3; + } + } + + getConditions(): Condition[] { + return this.conditions; + } + + getLinesWithCondition(line: string): string[] { + if (this.conditions.length === 1 && this.conditions[0].indicators.length === 0) { + return [line]; + } + let lines: string[] = []; + this.conditions.forEach((condition, cIdx) => { + let i = 0; + let line = ``; + condition.indicators.forEach(ind => { + if (i >= 3) { + lines.push(line.padEnd(16)); + i = 0; + } + if (i === 0) { + line = ` A${cIdx > 0 ? "O" : " "}`; + } + i++; + line += `${ind.negate ? `N` : ` `}${String(ind.indicator).padStart(2, '0')}`; + }); + lines.push(line.padEnd(16)); + }); + lines[lines.length - 1] = lines[lines.length - 1].substring(0, 16) + line.substring(16); + return lines; + } + + isActive(indicators: DisplayFileIndicators): boolean { + if (this.conditions.length <= 1 && this.conditions[0].indicators.length === 0) { + return true; + } + this.conditions.forEach(cond => { + let condResult = true; + cond.indicators.forEach(ind => { + if ((!indicators.get(ind.indicator) && !ind.negate) || (indicators.get(ind.indicator) && ind.negate)) { + condResult = false; + } + }); + if (condResult) { + return true; + } + }); + return false; + } + +} + + +export class DisplayFileIndicators { + private indicators: boolean[] = Array(99).fill(false); + + set(indicator: number, state: boolean) { + if (indicator < 1 || indicator > 99) { + throw new Error(`Invalid indicator number`); + } + this.indicators[indicator - 1] = state; + } + + setAll(state: boolean) { + this.indicators.forEach(ind => ind = state); + } + + get(indicator: number) :boolean { + if (indicator < 1 || indicator > 99) { + throw new Error(`Invalid indicator number`); + } + return this.indicators[indicator - 1]; + } + } \ No newline at end of file diff --git a/webui/dspf.d.ts b/webui/dspf.d.ts index 9f2ff07..8619f1a 100644 --- a/webui/dspf.d.ts +++ b/webui/dspf.d.ts @@ -24,7 +24,7 @@ export declare class DisplayFile { }): { value: string; keywords: Keyword[]; - conditions: Conditional[]; + conditional: Conditional; }; updateField(recordFormat: string, originalFieldName: string, fieldInfo: FieldInfo): { newLines: string[]; @@ -51,7 +51,7 @@ export declare class RecordInfo { export interface Keyword { name: string; value?: string; - conditions: Conditional[]; + conditional: Conditional; } export type DisplayType = "input" | "output" | "both" | "const" | "hidden"; export declare class FieldInfo { @@ -73,13 +73,19 @@ export declare class FieldInfo { [lineIndex: number]: string; }; }; - conditions: Conditional[]; + conditional: Conditional; keywords: Keyword[]; constructor(startRange: number, name?: string | undefined); handleKeywords(): void; } export declare class Conditional { + conditions: Condition[]; +} +export declare class Condition { + indicators: Indicator[]; +} +export declare class Indicator { indicator: number; negate: boolean; - constructor(indicator: number, negate?: boolean); } + diff --git a/webui/main.js b/webui/main.js index 4259145..8f3633c 100644 --- a/webui/main.js +++ b/webui/main.js @@ -3,6 +3,7 @@ * @typedef {import('./dspf.d.ts').RecordInfo} RecordInfo * @typedef {import('./dspf.d.ts').FieldInfo} FieldInfo * @typedef {import('./dspf.d.ts').Keyword} Keyword + * @typedef {import('./dspf.d.ts').Conditional} Conditional * @typedef {import("konva").default.Rect} Rect * @typedef {import("konva").default.Stage} Stage * @typedef {import("konva").default.Group} Group @@ -261,13 +262,17 @@ function renderSelectedFormat(layer, format) { windowTitle.keywords.push({ name: `COLOR`, value: parts[index + 1], - conditions: [] + conditional: { + conditions: [{indicators: []}] + } }); case `*DSPATR`: windowTitle.keywords.push({ name: `DSPATR`, value: parts[index + 1], - conditions: [] + conditional: { + conditions: [{indicators: []}] + } }); break; @@ -289,7 +294,9 @@ function renderSelectedFormat(layer, format) { windowTitle.keywords.push({ name: `COLOR`, value: `BLU`, - conditions: [] + conditional: { + conditions: [{indicators: []}] + } }); } @@ -409,12 +416,12 @@ function addFieldsToLayer(layer, format) { fields.forEach(field => { let canDisplay = true; - field.conditions.forEach(cond => { - // TODO: indicator support? + // TODO: indicator support? + //field.conditions.forEach(cond => { // if (this.indicators[cond.indicator] !== (cond.negate ? false : true)) { // canDisplay = false; // } - }); + //}); if (canDisplay) { const content = getElement(field); @@ -810,7 +817,7 @@ function clearFieldInfo() { }; return button; - } + }; // Creates: Secondary button @@ -1028,11 +1035,12 @@ function createKeywordPanel(id, inputKeywords, onUpdate) { value: keyword, description: keyword.value, actions, - subItems: keyword.conditions.map(c => ({ - label: String(c.indicator), - description: c.negate ? `Negated` : undefined, + // TODO: Support multiple conditions (or-groups) + subItems: keyword.conditional.conditions[0].indicators.map(ind => ({ + label: String(ind.indicator), + description: ind.negate ? `Negated` : undefined, icons - })), + })) }; }); }; @@ -1294,25 +1302,27 @@ function editKeyword(onUpdate, keyword) { const newKeyword = { name: keywordName, value: keywordValue ? keywordValue : undefined, - conditions: [] + conditional: { + conditions: [{indicators: []}] + } }; if (ind1 !== `None`) { - newKeyword.conditions.push({ + newKeyword.conditional.conditions[0].indicators.push({ indicator: ind1, negate: neg1 }); } if (ind2 !== `None`) { - newKeyword.conditions.push({ + newKeyword.conditional.conditions[0].indicators.push({ indicator: ind2, negate: neg2 }); } if (ind3 !== `None`) { - newKeyword.conditions.push({ + newKeyword.conditional.conditions[0].indicators.push({ indicator: ind3, negate: neg3 });