From dbc40699c7251f2fc9d4da0e3e6668578bf07136 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Fri, 4 Sep 2020 12:48:21 +0200
Subject: [PATCH] [dome] form warnings

---
 ivette/src/dome/src/renderer/layout/form.tsx | 65 ++++++++++++++++----
 1 file changed, 53 insertions(+), 12 deletions(-)

diff --git a/ivette/src/dome/src/renderer/layout/form.tsx b/ivette/src/dome/src/renderer/layout/form.tsx
index 165b1e54e96..3d814e2ce1c 100644
--- a/ivette/src/dome/src/renderer/layout/form.tsx
+++ b/ivette/src/dome/src/renderer/layout/form.tsx
@@ -9,11 +9,12 @@
 
 import { debounce } from 'lodash';
 import React from 'react';
-import * as Utils from 'dome/utils';
+import { SVG } from 'dome/controls/icons';
+import * as Utils from 'dome/misc/utils';
 
 export type Error = undefined | string;
 export type Setter<A> = (value: A) => void;
-export type Checker<A> = (value: A) => true | Error;
+export type Checker<A> = (value: A) => boolean | Error;
 export type State<A> = [A, Setter<A>]
 export type Callback<A> = (value: A, valid: boolean) => void;
 
@@ -24,15 +25,23 @@ export type Callback<A> = (value: A, valid: boolean) => void;
 export function inRange(
   a: number,
   b: number,
-  msg?: string,
 ): Checker<number> {
-  return (v: number) => (a <= v && v <= b) || msg || 'Invalid Range';
+  return (v: number) => (a <= v && v <= b);
 }
 
-export function validate<A>(value: A, checker?: Checker<A>): Error {
+export function validate<A>(
+  value: A,
+  checker: undefined | Checker<A>,
+  message: undefined | string,
+): Error {
   if (checker) {
-    const r = checker(value);
-    return r === true ? undefined : r;
+    try {
+      const r = checker(value);
+      if (r === undefined || r === true) return undefined;
+      return message || r || 'Invalid Field';
+    } catch (err) {
+      return err.toString();
+    }
   }
   return undefined;
 }
@@ -44,7 +53,7 @@ export function useCallback<A>(
   error: Error,
   onChange?: Callback<A>,
 ) {
-  React.useMemo(
+  React.useEffect(
     () => { if (onChange) onChange(value, isValid(error)); },
     [value, error, onChange],
   );
@@ -130,6 +139,7 @@ export function Filter(props: FilterProps & Children) {
 export interface FieldProps<A> {
   state: State<A>;
   checker?: Checker<A>;
+  message?: string;
   onChange?: Callback<A>;
   latency?: number;
 }
@@ -137,21 +147,21 @@ export interface FieldProps<A> {
 type FilterState<A> = [A, Setter<A>, Error];
 
 export function useField<A>(props: FieldProps<A>): FilterState<A> {
-  const { checker, latency = 0, onChange } = props;
+  const { checker, message, latency = 0, onChange } = props;
   const [value, setValue] = props.state;
   const [current, setCurrent] = React.useState<A>(value);
   const [error, setError] = React.useState<Error>(undefined);
   const update = React.useMemo(() => {
     if (!latency)
       return (newValue: A) => {
-        const newError = validate(newValue, checker);
+        const newError = validate(newValue, checker, message);
         setCurrent(newValue);
         setValue(newValue);
         setError(newError);
         if (onChange) onChange(newValue, isValid(newError));
       };
     const propagate = debounce((newValue) => {
-      const newError = validate(newValue);
+      const newError = validate(newValue, checker, message);
       setValue(newValue);
       setError(newError);
       if (onChange) onChange(newValue, isValid(newError));
@@ -160,7 +170,7 @@ export function useField<A>(props: FieldProps<A>): FilterState<A> {
       setCurrent(newValue);
       propagate(newValue);
     }
-  }, [checker, latency, onChange, setValue, setError]);
+  }, [checker, message, latency, onChange, setValue, setError]);
   return [current, update, error];
 };
 
@@ -188,4 +198,35 @@ export const Form = (props: FormProps) => {
   );
 }
 
+export interface WarningProps {
+  /** Short warn message in case of error. */
+  warn?: string;
+  /** Error description (in tooltip if warn, on hover otherwized). */
+  error?: Error;
+  /** Label offset. */
+  offset?: number;
+}
+
+/** Warning badge */
+export function Warning(props: WarningProps) {
+  const { error, warn, offset = 0 } = props;
+  if (!error) return null;
+  const hovered = warn ? warn : error;
+  const tooltip = warn ? error : undefined;
+  const style = warn ? { width: 'max-content' } : undefined;
+
+  return (
+    <div
+      className="dome-xIcon dome-xForm-error"
+      style={{ top: offset - 2 }} >
+      <SVG id="WARNING" size={11} title={tooltip} />
+      <span
+        className="dome-xForm-warning"
+        style={style}>
+        {hovered}
+      </span>
+    </div>
+  );
+};
+
 // --------------------------------------------------------------------------
-- 
GitLab