From 0c681538a68b1dcb08e211b1a3586a2c429e44fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= Date: Sun, 3 Aug 2025 20:56:36 +0200 Subject: [PATCH 1/7] Conditional class for managing conditions --- src/tests/dspf.test.ts | 17 +++++- src/ui/dspf.ts | 120 ++++++++++++++++++++++++----------------- 2 files changed, 88 insertions(+), 49 deletions(-) diff --git a/src/tests/dspf.test.ts b/src/tests/dspf.test.ts index 19514db..7da2971 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 } from "../ui/dspf"; +import { Conditional, DdsLineRange, DisplayFile } from "../ui/dspf"; describe('DisplayFile tests', () => { @@ -42,4 +42,19 @@ 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); + + }); + }); diff --git a/src/ui/dspf.ts b/src/ui/dspf.ts index 955c0ab..a5b8a3b 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 = ``; @@ -564,7 +529,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"; @@ -577,7 +542,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) {} @@ -593,7 +558,66 @@ export class FieldInfo { } } -export interface Conditional { - indicator: number, - negate: boolean -} \ No newline at end of file +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 >= 8) { + 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 >= 8) { + 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; + } + + getLines(line: string): string[] { + return []; + } + +} From 075de31f4495d2f5458bb818b19bbbe8fcd3978c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= Date: Mon, 4 Aug 2025 10:43:56 +0200 Subject: [PATCH 2/7] feat: support for conditions in getLinesForField --- src/tests/dspf.test.ts | 6 +++--- src/ui/dspf.ts | 30 ++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/tests/dspf.test.ts b/src/tests/dspf.test.ts index d8448b3..263b26c 100644 --- a/src/tests/dspf.test.ts +++ b/src/tests/dspf.test.ts @@ -78,12 +78,12 @@ describe('DisplayFile tests', () => { { name: "COLOR", value: "BLU", - conditions: [] + conditional: new Conditional() }, { name: "DSPATR", value: "PR", - conditions: [] + conditional: new Conditional(` 31`) } ); @@ -91,7 +91,7 @@ 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)`); }); diff --git a/src/ui/dspf.ts b/src/ui/dspf.ts index f925eb3..4eee41c 100644 --- a/src/ui/dspf.ts +++ b/src/ui/dspf.ts @@ -346,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; @@ -614,8 +611,29 @@ export class Conditional { return this.conditions; } - getLines(line: string): string[] { - return []; + 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; } } From bdd68ab0ab1cf05f7881063e5d0dbee19c67be71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= Date: Mon, 4 Aug 2025 16:04:12 +0200 Subject: [PATCH 3/7] feat: support for conditions in getLinesForFormat --- src/tests/dspf.test.ts | 10 ++++++++++ src/ui/dspf.ts | 8 +++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/tests/dspf.test.ts b/src/tests/dspf.test.ts index 263b26c..d665c7f 100644 --- a/src/tests/dspf.test.ts +++ b/src/tests/dspf.test.ts @@ -22,6 +22,8 @@ describe('DisplayFile tests', () => { ` A 20 DSPATR(PR) `, ` A COLOR(YLW) `, ` A FLD0102 10 B 3 5 `, + ` A R FORM2 `, + ` A 30 SLNO(02) `, ]; it('getRangeForFormat', () => { @@ -95,6 +97,14 @@ describe('DisplayFile tests', () => { }); + 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); diff --git a/src/ui/dspf.ts b/src/ui/dspf.ts index 4eee41c..aea0302 100644 --- a/src/ui/dspf.ts +++ b/src/ui/dspf.ts @@ -398,7 +398,6 @@ export class DisplayFile { return { newLines, range }; } - // TODO: test cases static getLinesForFormat(recordFormat: RecordInfo): string[] { const lines: string[] = []; @@ -407,10 +406,9 @@ export class DisplayFile { } for (const keyword of recordFormat.keywords) { - // TODO: support conditions - lines.push( - ` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`, - ); + for (const keyword of recordFormat.keywords) { + lines.push(...keyword.conditional.getLinesWithCondition(` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`)); + } } return lines; From 92b72fd780b2264cf166ff9da05ad1b8659d46c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= Date: Mon, 4 Aug 2025 20:02:31 +0200 Subject: [PATCH 4/7] feat: class for display file indicator status and method to evaluate if conditional is active --- src/tests/dspf.test.ts | 10 +++++++++- src/ui/dspf.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/tests/dspf.test.ts b/src/tests/dspf.test.ts index d665c7f..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 { Conditional, DdsLineRange, DisplayFile, FieldInfo } from "../ui/dspf"; +import { Conditional, DdsLineRange, DisplayFile, DisplayFileIndicators, FieldInfo } from "../ui/dspf"; describe('DisplayFile tests', () => { @@ -125,6 +125,14 @@ describe('DisplayFile tests', () => { 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 aea0302..1fd7e9c 100644 --- a/src/ui/dspf.ts +++ b/src/ui/dspf.ts @@ -634,4 +634,46 @@ export class Conditional { 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 From c3cdf00b344313de5e0655cfaf7279eb0d03ee78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= Date: Mon, 4 Aug 2025 20:12:34 +0200 Subject: [PATCH 5/7] fix: max number of conditions and indicators --- src/ui/dspf.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/dspf.ts b/src/ui/dspf.ts index 1fd7e9c..05b7f35 100644 --- a/src/ui/dspf.ts +++ b/src/ui/dspf.ts @@ -573,7 +573,7 @@ export class Conditional { push(indicatorStr: string) { if (indicatorStr.substring(0, 1) === `O` && this.conditions[this.conditions.length - 1].indicators.length > 0) { - if (this.conditions.length >= 8) { + if (this.conditions.length >= 9) { throw new Error("Too many conditions"); } this.conditions.push({indicators: []}); @@ -591,7 +591,7 @@ export class Conditional { 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 >= 8) { + 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({ From 14e97c46fb408ccf2048d0a3b7fd467516bebd4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= Date: Tue, 5 Aug 2025 19:26:27 +0200 Subject: [PATCH 6/7] Adopt Conditional class in frontend --- src/ui/dspf.ts | 12 +++++------- webui/dspf.d.ts | 17 +++++++++++++---- webui/main.js | 24 +++++++++++++----------- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/ui/dspf.ts b/src/ui/dspf.ts index 05b7f35..9328740 100644 --- a/src/ui/dspf.ts +++ b/src/ui/dspf.ts @@ -406,9 +406,7 @@ export class DisplayFile { } for (const keyword of recordFormat.keywords) { - for (const keyword of recordFormat.keywords) { - lines.push(...keyword.conditional.getLinesWithCondition(` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`)); - } + lines.push(...keyword.conditional.getLinesWithCondition(` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`)); } return lines; @@ -597,7 +595,7 @@ export class Conditional { this.conditions[this.conditions.length - 1].indicators.push({ indicator: indicator, negate: negate - }) + }); } } @@ -610,7 +608,7 @@ export class Conditional { } getLinesWithCondition(line: string): string[] { - if (this.conditions.length == 1 && this.conditions[0].indicators.length == 0) { + if (this.conditions.length === 1 && this.conditions[0].indicators.length === 0) { return [line]; } let lines: string[] = []; @@ -622,7 +620,7 @@ export class Conditional { lines.push(line.padEnd(16)); i = 0; } - if (i == 0) { + if (i === 0) { line = ` A${cIdx > 0 ? "O" : " "}`; } i++; @@ -635,7 +633,7 @@ export class Conditional { } isActive(indicators: DisplayFileIndicators): boolean { - if (this.conditions.length <= 1 && this.conditions[0].indicators.length == 0) { + if (this.conditions.length <= 1 && this.conditions[0].indicators.length === 0) { return true; } this.conditions.forEach(cond => { diff --git a/webui/dspf.d.ts b/webui/dspf.d.ts index 9f2ff07..011cc43 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,22 @@ 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[]; + //indicator: number; + //negate: boolean; + //constructor(indicator: number, negate?: boolean); +} +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..614506c 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,13 @@ function renderSelectedFormat(layer, format) { windowTitle.keywords.push({ name: `COLOR`, value: parts[index + 1], - conditions: [] + conditional: new Conditional() }); case `*DSPATR`: windowTitle.keywords.push({ name: `DSPATR`, value: parts[index + 1], - conditions: [] + conditional: new Conditional() }); break; @@ -289,7 +290,7 @@ function renderSelectedFormat(layer, format) { windowTitle.keywords.push({ name: `COLOR`, value: `BLU`, - conditions: [] + conditional: new Conditional() }); } @@ -409,12 +410,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); @@ -1028,11 +1029,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,7 +1296,7 @@ function editKeyword(onUpdate, keyword) { const newKeyword = { name: keywordName, value: keywordValue ? keywordValue : undefined, - conditions: [] + conditional: new Conditional() }; if (ind1 !== `None`) { From 157c254ceadefc7f4d94c1d0ca11a3e2a0d76b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= Date: Sat, 16 Aug 2025 08:27:23 +0200 Subject: [PATCH 7/7] fix Conditional in main.js --- webui/dspf.d.ts | 3 --- webui/main.js | 24 ++++++++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/webui/dspf.d.ts b/webui/dspf.d.ts index 011cc43..8619f1a 100644 --- a/webui/dspf.d.ts +++ b/webui/dspf.d.ts @@ -80,9 +80,6 @@ export declare class FieldInfo { } export declare class Conditional { conditions: Condition[]; - //indicator: number; - //negate: boolean; - //constructor(indicator: number, negate?: boolean); } export declare class Condition { indicators: Indicator[]; diff --git a/webui/main.js b/webui/main.js index 614506c..8f3633c 100644 --- a/webui/main.js +++ b/webui/main.js @@ -262,13 +262,17 @@ function renderSelectedFormat(layer, format) { windowTitle.keywords.push({ name: `COLOR`, value: parts[index + 1], - conditional: new Conditional() + conditional: { + conditions: [{indicators: []}] + } }); case `*DSPATR`: windowTitle.keywords.push({ name: `DSPATR`, value: parts[index + 1], - conditional: new Conditional() + conditional: { + conditions: [{indicators: []}] + } }); break; @@ -290,7 +294,9 @@ function renderSelectedFormat(layer, format) { windowTitle.keywords.push({ name: `COLOR`, value: `BLU`, - conditional: new Conditional() + conditional: { + conditions: [{indicators: []}] + } }); } @@ -811,7 +817,7 @@ function clearFieldInfo() { }; return button; - } + }; // Creates: Secondary button @@ -1296,25 +1302,27 @@ function editKeyword(onUpdate, keyword) { const newKeyword = { name: keywordName, value: keywordValue ? keywordValue : undefined, - conditional: new Conditional() + 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 });