From 29626bb71bff87045b1e64b2d1dc9308c414f257 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Tue, 26 Nov 2024 23:17:25 +0100 Subject: [PATCH] feat(ui): implement `CoreProgressBar` - WF-60 --- .../public/components/progressbar.png | Bin 0 -> 6831 bytes .../components/core/base/BaseInputWrapper.vue | 10 +- .../core/content/CoreProgressBar.vue | 191 ++++++++++++++++++ src/ui/src/composables/useFieldValue.ts | 33 +++ src/ui/src/composables/useFormater.ts | 18 ++ src/ui/src/core/templateMap.ts | 2 + src/ui/src/renderer/syntheticEvents.ts | 5 +- 7 files changed, 253 insertions(+), 6 deletions(-) create mode 100644 docs/framework/public/components/progressbar.png create mode 100644 src/ui/src/components/core/content/CoreProgressBar.vue create mode 100644 src/ui/src/composables/useFieldValue.ts create mode 100644 src/ui/src/composables/useFormater.ts diff --git a/docs/framework/public/components/progressbar.png b/docs/framework/public/components/progressbar.png new file mode 100644 index 0000000000000000000000000000000000000000..8725283529506d0d09cc95dba2bb6730b1697c2e GIT binary patch literal 6831 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 JMYqkK|3C4EIHLdn literal 0 HcmV?d00001 diff --git a/src/ui/src/components/core/base/BaseInputWrapper.vue b/src/ui/src/components/core/base/BaseInputWrapper.vue index 1cb3b391b..ed3b64273 100644 --- a/src/ui/src/components/core/base/BaseInputWrapper.vue +++ b/src/ui/src/components/core/base/BaseInputWrapper.vue @@ -4,16 +4,16 @@ class="BaseInputWrapper" :class="{ horizontal: isHorizontal }" > - + diff --git a/src/ui/src/composables/useFieldValue.ts b/src/ui/src/composables/useFieldValue.ts new file mode 100644 index 000000000..ed77bb76f --- /dev/null +++ b/src/ui/src/composables/useFieldValue.ts @@ -0,0 +1,33 @@ +import { ComputedRef, computed } from "vue"; + +type Fields = Record>; + +export function useFieldValueAsString( + fields: Fields, + key: string, + fallback?: string, +): ComputedRef { + return computed(() => + fields[key]?.value === undefined + ? fallback + : String(fields[key]?.value), + ); +} + +export function useFieldValueAsNumber( + fields: Fields, + key: string, + fallback?: number, +): ComputedRef { + return computed(() => { + if (fields[key]?.value === undefined) return fallback; + return Number(fields[key]?.value); + }); +} + +export function useFieldValueAsYesNo( + fields: Fields, + key: string, +): ComputedRef { + return computed(() => fields[key].value === "yes"); +} diff --git a/src/ui/src/composables/useFormater.ts b/src/ui/src/composables/useFormater.ts new file mode 100644 index 000000000..339229769 --- /dev/null +++ b/src/ui/src/composables/useFormater.ts @@ -0,0 +1,18 @@ +import { computed, ComputedRef } from "vue"; + +export function usePercentageFormater( + number: ComputedRef, + options: Pick< + Intl.NumberFormatOptions, + "minimumFractionDigits" | "maximumFractionDigits" + > = { + minimumFractionDigits: 0, + maximumFractionDigits: 1, + }, +) { + const formatter = new Intl.NumberFormat(undefined, { + style: "percent", + ...options, + }); + return computed(() => formatter.format(number.value)); +} diff --git a/src/ui/src/core/templateMap.ts b/src/ui/src/core/templateMap.ts index c3031ccbd..ec4b344f2 100644 --- a/src/ui/src/core/templateMap.ts +++ b/src/ui/src/core/templateMap.ts @@ -17,6 +17,7 @@ import CoreTags from "../components/core/content/CoreTags.vue"; import CoreAvatar from "../components/core/content/CoreAvatar.vue"; import CoreAnnotatedText from "../components/core/content/CoreAnnotatedText.vue"; import CoreJsonViewer from "../components/core/content/CoreJsonViewer.vue"; +import CoreProgressBar from "../components/core/content/CoreProgressBar.vue"; // input import CoreCheckboxInput from "../components/core/input/CoreCheckboxInput.vue"; @@ -136,6 +137,7 @@ const templateMap: TemplateMap = { jsonviewer: CoreJsonViewer, workflows_root: WorkflowsRoot, workflows_workflow: WorkflowsWorkflow, + progressbar: CoreProgressBar, }; const abstractTemplateMap: Record = {}; diff --git a/src/ui/src/renderer/syntheticEvents.ts b/src/ui/src/renderer/syntheticEvents.ts index df7bab08c..5fcf3b229 100644 --- a/src/ui/src/renderer/syntheticEvents.ts +++ b/src/ui/src/renderer/syntheticEvents.ts @@ -1,4 +1,7 @@ -export function getClick(ev: MouseEvent): CustomEvent { +/** + * @param ev event from a mouse click, or a keyboard click (using tab navigation, then click with `Enter`) + */ +export function getClick(ev: MouseEvent | KeyboardEvent): CustomEvent { const payload = { ctrlKey: ev.ctrlKey, shiftKey: ev.shiftKey,