From 05ee4617d54b49fcd7bf8898434dc136734c9462 Mon Sep 17 00:00:00 2001 From: faiztyanirh Date: Mon, 9 Mar 2026 17:09:53 +0700 Subject: [PATCH] refactor calc formula builder to token --- .../test/page/tabs/calculation.svelte | 255 ++++-- .../test/page/tabs/calculation09032026.svelte | 128 +++ .../form/dictionary-form-renderer.svelte | 316 +++++-- .../dictionary-form-renderer09032026.svelte | 770 ++++++++++++++++++ 4 files changed, 1329 insertions(+), 140 deletions(-) create mode 100644 src/lib/components/dictionary/test/page/tabs/calculation09032026.svelte create mode 100644 src/lib/components/reusable/form/dictionary-form-renderer09032026.svelte diff --git a/src/lib/components/dictionary/test/page/tabs/calculation.svelte b/src/lib/components/dictionary/test/page/tabs/calculation.svelte index 9741cf0..cb3d6fa 100644 --- a/src/lib/components/dictionary/test/page/tabs/calculation.svelte +++ b/src/lib/components/dictionary/test/page/tabs/calculation.svelte @@ -11,42 +11,135 @@ let props = $props(); const operators = ['+', '-', '*', '/', '^', '(', ')']; - let expression = $state(''); - let cursorPosition = $state(0); + const logicalop = ['IF', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT', 'MIN', 'MAX']; + const comparisonop = ['=', '!=', '<', '>', '<=', '>=']; - function unselectAll(key) { + let tokens = $state([]); + let cursorIndex = $state(0); + let showLiteralPopover = $state(false); + let literalPopoverType = $state(('string')); +$inspect(tokens) + // let expression = $state(''); + // let cursorPosition = $state(0); + + function uid() { + return Math.random().toString(36).slice(2, 9); + } + + function insertToken(type, value) { + const token = { id: uid(), type, value }; + tokens = [...tokens.slice(0, cursorIndex), token, ...tokens.slice(cursorIndex)]; + cursorIndex += 1; + syncFormState(); + } + + function addValue(val) { + insertToken('test', val); + } + + function addOperator(op) { + const isLogical = logicalop.includes(op); + const isComparison = comparisonop.includes(op); + const type = isLogical ? 'keyword' : isComparison ? 'comparator' : 'operator'; + insertToken(type, op); + } + + function addLiteral(val) { + insertToken('literal', val); + } + + function addNewline() { + insertToken('newline', '\n'); + } + + function moveCursorLeft() { + if (cursorIndex > 0) cursorIndex -= 1; + } + + function moveCursorRight() { + if (cursorIndex < tokens.length) cursorIndex += 1; + } + + function deleteChar() { + if (cursorIndex > 0) { + tokens = [...tokens.slice(0, cursorIndex - 1), ...tokens.slice(cursorIndex)]; + cursorIndex -= 1; + syncFormState(); + } + } + + function clearExpression() { + tokens = []; + cursorIndex = 0; + syncFormState(); + } + + function syncFormState() { + const code = tokens + .map((t) => (t.type === 'newline' ? '\n' : t.value)) + .join(' ') + .replace(/ \n /g, '\n'); + props.calFormState.form.FormulaCode = code; + props.calFormState.validateField?.('FormulaCode', code, false); + } + + function getErrorStatus() { + const selected = props.calFormState.form.FormulaInput; + if (!Array.isArray(selected)) return []; + return selected.map((item) => ({ + value: item.value, + done: tokens.some((t) => t.type === 'test' && t.value === item.value) + })); + } + + function openLiteralPopover(type) { + literalPopoverType = type; + showLiteralPopover = true; + } + + function onLiteralConfirm(val) { + addLiteral(val); + showLiteralPopover = false; + } + + function unselectAll(key) { props.calFormState.form[key] = []; props.calFormState.validateField?.(key, [], false); } - function getErrorStatus(formulaCode = '') { - const selected = props.calFormState.form.FormulaInput; - if (!Array.isArray(selected)) return []; + // function unselectAll(key) { + // props.calFormState.form[key] = []; + // props.calFormState.validateField?.(key, [], false); + // } - return selected.map((item) => ({ - value: item.value, - done: new RegExp(`\\b${item.value}\\b`, 'i').test(formulaCode) - })); - } + // function getErrorStatus(formulaCode = '') { + // const selected = props.calFormState.form.FormulaInput; + // if (!Array.isArray(selected)) return []; - function addToExpression(text) { - const before = expression.slice(0, cursorPosition); - const after = expression.slice(cursorPosition); - expression = before + text + after; - cursorPosition += text.length; - } + // return selected.map((item) => ({ + // value: item.value, + // done: new RegExp(`\\b${item.value}\\b`, 'i').test(formulaCode) + // })); + // } - function addOperator(op) { - addToExpression(op); - props.calFormState.form.FormulaCode = expression; - props.calFormState.validateField?.('FormulaCode', expression, false); - } + // function addToExpression(text) { + // const before = expression.slice(0, cursorPosition); + // const after = expression.slice(cursorPosition); + // expression = before + text + after; + // cursorPosition += text.length; + // } - function addValue(val) { - addToExpression(val); - props.calFormState.form.FormulaCode = expression; - props.calFormState.validateField?.('FormulaCode', expression, false); - } + // function addOperator(op) { + // addToExpression(op); + // props.calFormState.form.FormulaCode = expression; + // props.calFormState.validateField?.('FormulaCode', expression, false); + // } + + // function addValue(val) { + // addToExpression(val); + // props.calFormState.form.FormulaCode = expression; + // props.calFormState.validateField?.('FormulaCode', expression, false); + // } // function handleInput(e) { // expression = e.target.value; @@ -59,63 +152,67 @@ // cursorPosition = e.target.selectionStart; // } - function handleContainerClick(e) { - const rect = e.currentTarget.getBoundingClientRect(); - const text = expression; - const charWidth = 8.5; - const padding = 12; - const clickX = e.clientX - rect.left - padding; - let newPos = Math.floor(clickX / charWidth); - newPos = Math.max(0, Math.min(newPos, text.length)); - cursorPosition = newPos; - } + // function handleContainerClick(e) { + // const rect = e.currentTarget.getBoundingClientRect(); + // const text = expression; + // const charWidth = 8.5; + // const padding = 12; + // const clickX = e.clientX - rect.left - padding; + // let newPos = Math.floor(clickX / charWidth); + // newPos = Math.max(0, Math.min(newPos, text.length)); + // cursorPosition = newPos; + // } - function moveCursorLeft() { - if (cursorPosition > 0) { - cursorPosition -= 1; - } - } + // function moveCursorLeft() { + // if (cursorPosition > 0) { + // cursorPosition -= 1; + // } + // } - function moveCursorRight() { - if (cursorPosition < expression.length) { - cursorPosition += 1; - } - } + // function moveCursorRight() { + // if (cursorPosition < expression.length) { + // cursorPosition += 1; + // } + // } - function deleteChar() { - if (cursorPosition > 0) { - const before = expression.slice(0, cursorPosition - 1); - const after = expression.slice(cursorPosition); - expression = before + after; - cursorPosition -= 1; - props.calFormState.form.FormulaCode = expression; - props.calFormState.validateField?.('FormulaCode', expression, false); - } - } + // function deleteChar() { + // if (cursorPosition > 0) { + // const before = expression.slice(0, cursorPosition - 1); + // const after = expression.slice(cursorPosition); + // expression = before + after; + // cursorPosition -= 1; + // props.calFormState.form.FormulaCode = expression; + // props.calFormState.validateField?.('FormulaCode', expression, false); + // } + // } - function clearExpression() { - expression = ''; - cursorPosition = 0; - props.calFormState.form.FormulaCode = expression; - props.calFormState.validateField?.('FormulaCode', expression, false); - } + // function clearExpression() { + // expression = ''; + // cursorPosition = 0; + // props.calFormState.form.FormulaCode = expression; + // props.calFormState.validateField?.('FormulaCode', expression, false); + // }
- + (cursorIndex = i)} + />
\ No newline at end of file diff --git a/src/lib/components/dictionary/test/page/tabs/calculation09032026.svelte b/src/lib/components/dictionary/test/page/tabs/calculation09032026.svelte new file mode 100644 index 0000000..a64e426 --- /dev/null +++ b/src/lib/components/dictionary/test/page/tabs/calculation09032026.svelte @@ -0,0 +1,128 @@ + + +
+ +
\ No newline at end of file diff --git a/src/lib/components/reusable/form/dictionary-form-renderer.svelte b/src/lib/components/reusable/form/dictionary-form-renderer.svelte index 1f461bd..5f5ab99 100644 --- a/src/lib/components/reusable/form/dictionary-form-renderer.svelte +++ b/src/lib/components/reusable/form/dictionary-form-renderer.svelte @@ -1,6 +1,7 @@ {#snippet Fieldset({ @@ -285,7 +301,7 @@ formState.form[key] = selectedObjects; if (validateOn?.includes('input')) { formState.validateField?.(key, selectedObjects, false); - formState.validateField?.('FormulaCode', expression, false); + formState.validateField?.('FormulaCode', formState.form.FormulaCode ?? '', false); } }} onOpenChange={(open) => { @@ -470,69 +486,246 @@ -
- {/each} - {#if cursorPosition === expression.length} - + {#if line.length > 0 && cursorIndex === line[line.length - 1].idx + 1} + {/if} - -
+ + {/each} + {#if cursorIndex === tokens.length && (tokens.length === 0 || tokens[tokens.length - 1].type === 'newline')} + + {/if} + {/if} + - - - + + + + + - {#if formState.form.FormulaInput.length > 0} -
-
- Selected Values -
- {#each formState.form.FormulaInput as item (item)} + {#if formState.form.FormulaInput?.length > 0} +
+ +
+ Selected Tests +
+ {#each formState.form.FormulaInput as item (item)} + + {/each} +
+
+ + +
+ Custom Values +
+ + + + {#snippet child({ props: triggerProps })} + + {/snippet} + + +
+
+

Enter Text Value

+ { + if (e.key === 'Enter' && stringLiteralInput.trim()) { + onAddLiteral(`"${stringLiteralInput.trim()}"`); + stringLiteralInput = ''; + } + }} + /> +
- {/each} -
-
- Operators -
- {#each operators as op} - - {/each} +
-
-
- {/if} + + + + + + + {#snippet child({ props: triggerProps })} + + {/snippet} + + +
+
+

Enter Number Value

+ { + if (e.key === 'Enter' && numberLiteralInput != null && !isNaN(numberLiteralInput)) { + onAddLiteral(String(numberLiteralInput)); + numberLiteralInput = null; + } + }} + /> +
+ +
+
+
+
+
+
+
+ + +
+ Logical Operators +
+ {#each logicalop as op} + + {/each} +
+
+ + +
+ Comparison Operators +
+ {#each comparisonop as op} + + {/each} +
+
+ + +
+ Math Operators +
+ {#each operators as op} + + {/each} +
+
+ + + {#if tokens.length > 0} +
+ +
{expressionString}
+
+ {/if} +
+ {/if}
{:else if type === 'members'} {@const filteredOptions = getFilteredOptions(key)} @@ -622,7 +815,8 @@
{/if} {#if key === 'FormulaCode' && formState.form.FormulaInput?.length} - {@const inputStatus = onGetErrorStatus?.(expression)} + {@const inputStatus = onGetErrorStatus?.()} +
Must included : diff --git a/src/lib/components/reusable/form/dictionary-form-renderer09032026.svelte b/src/lib/components/reusable/form/dictionary-form-renderer09032026.svelte new file mode 100644 index 0000000..e7689d9 --- /dev/null +++ b/src/lib/components/reusable/form/dictionary-form-renderer09032026.svelte @@ -0,0 +1,770 @@ + + +{#snippet Fieldset({ + key, + label, + required, + type, + optionsEndpoint, + options, + validateOn, + dependsOn, + endpointParamKey, + valueKey, + labelKey, + txtKey +})} +
+
+ + {#if required} + * + {/if} +
+ +
+ {#if type === 'text'} + { + if (validateOn?.includes('input')) { + formState.validateField(key, formState.form[key], false); + } + }} + onblur={() => { + if (validateOn?.includes('blur')) { + validateFieldAsync(key, mode, originalData?.[key]); + } + }} + readonly={key === 'NumRefType' || key === 'TxtRefType' || key === 'Level'} + /> + {:else if type === 'email'} + { + if (validateOn?.includes('input')) { + formState.validateField(key, formState.form[key], false); + } + }} + onblur={() => { + if (validateOn?.includes('blur')) { + formState.validateField(key, formState.form[key], false); + } + }} + /> + {:else if type === 'number'} + { + if (validateOn?.includes('input')) { + formState.validateField(key, formState.form[key], false); + } + }} + onblur={() => { + if (validateOn?.includes('blur')) { + formState.validateField(key, formState.form[key], false); + } + }} + onkeydown={(e) => ['e', 'E', '+', '-'].includes(e.key) && e.preventDefault()} + /> + {:else if type === 'textarea'} +