From 209af7e7dca1c022427d69b7faf55a17c0ed2b8e Mon Sep 17 00:00:00 2001
From: rlazarini <remi.lazarini@cea.fr>
Date: Tue, 16 Apr 2024 16:27:06 +0200
Subject: [PATCH] [Ivette] restructuring action + style block form

---
 ivette/src/dome/renderer/dark.css         |   1 +
 ivette/src/dome/renderer/layout/forms.tsx | 145 +++++++++++-----------
 ivette/src/dome/renderer/layout/style.css |  84 ++++++++-----
 ivette/src/dome/renderer/light.css        |   1 +
 ivette/src/renderer/Messages.tsx          |   4 +-
 ivette/src/renderer/Preferences.tsx       |   4 +-
 6 files changed, 131 insertions(+), 108 deletions(-)

diff --git a/ivette/src/dome/renderer/dark.css b/ivette/src/dome/renderer/dark.css
index 66d7f12bb14..21ac1aabece 100644
--- a/ivette/src/dome/renderer/dark.css
+++ b/ivette/src/dome/renderer/dark.css
@@ -32,6 +32,7 @@
     --background-alterning-even: #475366;
     --background-interaction: #4c596b;
     --background-report: #1e2b3d;
+    --background-block-form: #2d3e58;
 
     --selected-element: #082032;
     --checked-element: #68758e;
diff --git a/ivette/src/dome/renderer/layout/forms.tsx b/ivette/src/dome/renderer/layout/forms.tsx
index 0b3baaf2123..3b1eb680774 100644
--- a/ivette/src/dome/renderer/layout/forms.tsx
+++ b/ivette/src/dome/renderer/layout/forms.tsx
@@ -40,11 +40,11 @@
 
 import { debounce } from 'lodash';
 import Events from 'events';
-import React from 'react';
+import React, { Children } from 'react';
 import * as Dome from 'dome';
 import * as Utils from 'dome/misc/utils';
 import { Hbox } from 'dome/layout/boxes';
-import { Icon, IconKind, IconProps, SVG } from 'dome/controls/icons';
+import { Icon, IconKind, SVG } from 'dome/controls/icons';
 import { Checkbox, Radio, Select as SelectMenu } from 'dome/controls/buttons';
 import { Label } from 'dome/controls/labels';
 
@@ -637,7 +637,7 @@ export function useIndex<A>(
 /* --- Form Filter Context                                                ---*/
 /* --------------------------------------------------------------------------*/
 
-type modeForm = "grid" | "block";
+type formLayout = "page" | "sidebar";
 export interface FilterProps {
   /** default is false. */
   hidden?: boolean;
@@ -654,7 +654,7 @@ export interface Children { children?: React.ReactNode }
 interface FormContext {
   disabled: boolean;
   hidden: boolean;
-  mode: modeForm;
+  layout: formLayout;
 }
 
 const CONTEXT = React.createContext<FormContext | undefined>(undefined);
@@ -667,14 +667,14 @@ const DISABLED =
   ({ disabled = false, enabled = true }: FilterProps): boolean =>
     disabled || !enabled;
 
-const DEFAULT_MODE = "grid";
+const DEFAULT_MODE = "page";
 
 function useContext(props?: FilterProps): FormContext {
   const Parent = React.useContext(CONTEXT);
   return {
     hidden: (props && HIDDEN(props)) || (Parent?.hidden ?? false),
     disabled: (props && DISABLED(props)) || (Parent?.disabled ?? false),
-    mode: Parent?.mode ?? DEFAULT_MODE,
+    layout: Parent?.layout ?? DEFAULT_MODE,
   };
 }
 
@@ -697,27 +697,31 @@ export function FormFilter(props: FilterProps & Children): JSX.Element | null {
 /* --------------------------------------------------------------------------*/
 
 /** @category Form Containers */
-export interface PageProps extends FilterProps, Children {
+export interface FormProps extends FilterProps, Children {
   /** Additional container class. */
   className?: string;
   /** Additional container style. */
   style?: React.CSSProperties;
   /** Page mode */
-  mode?: modeForm;
+  layout?: formLayout;
 }
 
 /**
    Main Form Container.
    @category Form Containers
  */
-export function Page(props: PageProps): JSX.Element | null {
-  const { className, style, children, mode = DEFAULT_MODE, ...filter } = props;
+export function Form(props: FormProps): JSX.Element | null {
+  const { className, style, children,
+    layout = DEFAULT_MODE, ...filter } = props;
   const { hidden, disabled } = useContext(filter);
-  const css = Utils.classes('dome-xForm-grid', className);
+  const css = Utils.classes(
+    'dome-xForm-'+(layout === DEFAULT_MODE ? "grid" : layout),
+    className
+  );
   if (hidden) return null;
   return (
     <div className={css} style={style}>
-      <CONTEXT.Provider value={{ hidden, disabled, mode }}>
+      <CONTEXT.Provider value={{ hidden, disabled, layout }}>
         {children}
       </CONTEXT.Provider>
     </div>
@@ -800,7 +804,7 @@ export interface SectionProps extends FilterProps, Children {
 /** @category Form Fields */
 export function Section(props: SectionProps): JSX.Element | null {
   const { label, title, children, warning, error, ...filter } = props;
-  const { disabled, hidden, mode } = useContext(filter);
+  const { disabled, hidden, layout } = useContext(filter);
   const [unfold, flip] = Dome.useFlipSettings(props.settings, props.unfold);
 
   if (hidden) return null;
@@ -813,7 +817,7 @@ export function Section(props: SectionProps): JSX.Element | null {
   );
 
   return (
-    <CONTEXT.Provider value={{ hidden, disabled, mode }}>
+    <CONTEXT.Provider value={{ hidden, disabled, layout }}>
       <div className="dome-xForm-section" onClick={flip}>
         <div className="dome-xForm-fold">
           <SVG id={unfold ? 'TRIANGLE.DOWN' : 'TRIANGLE.RIGHT'} size={11} />
@@ -834,72 +838,70 @@ export function Section(props: SectionProps): JSX.Element | null {
 /* --------------------------------------------------------------------------*/
 
 /** @category Form Fields Actions*/
-export type ActionsProps = IconProps
+export type ActionsButtonProps<A> = {
+  state: FieldState<A>,
+  title?: string,
+  equal?: (a: A, b: A) => boolean
+}
 
 /** @category Form Fields Actions*/
-export function resetActions<A>(
-  fieldState: FieldState<A>,
-  equal?: (a: A, b: A) => boolean
-): ActionsProps | null {
-  const { value, reset, onChanged } = fieldState;
-
-  if(reset === undefined) return null;
-
-  const resetActions = {
-    id: "RELOAD",
-    title: "Reset",
-    size: 12,
-    kind: "default" as IconKind,
-    onClick: () => {
-      onChanged(reset, undefined, true);
-    }
-  };
-  if(equal ? equal(reset, value) : reset === value) {
-    resetActions.title = "Field modified";
-    resetActions.kind = "warning" as IconKind;
-  }
-  return resetActions;
+export function ResetButton<A>(
+  props: ActionsButtonProps<A>
+): JSX.Element | null {
+  const { state, title, equal } = props;
+  const { value, reset, onChanged } = state;
+
+  if(!isResetAble(state)) return null;
+
+  const isEqual = compare(equal, reset as A, value);
+  return (
+    <Icon
+      id = "RELOAD"
+      title = {(title ?? "Reset")+(isEqual ? " : Field modified": "")}
+      size = {12}
+      kind ={isEqual ? "warning" as IconKind : "default" as IconKind}
+      onClick = {() => {
+        onChanged(reset as A, undefined, true);
+      }}
+    />
+  );
 }
 
 /** @category Form Fields Actions*/
-export function commitActions<A>(
-  fieldState: FieldState<A>,
-  equal?: (a: A, b: A) => boolean
-): ActionsProps | null {
-  const { value, error, reset, onChanged } = fieldState;
+export function CommitButton<A>(
+  props: ActionsButtonProps<A>
+): JSX.Element | null {
+  const { state, title, equal } = props;
+  const { value, error, reset, onChanged } = state;
 
   const commitAble = Boolean(
-    (reset !== undefined) &&
-    (equal ? !equal(reset, value) : reset !== value) &&
-    isValid(error)
+    isCommitAble(state) &&
+    (!compare(equal, reset as A, value))
   );
   if(!commitAble) return null;
-  const commitAction = {
-    id: "PUSH",
-    title: "Update FC",
-    size: 14,
-    onClick: () => {
-      onChanged(value, error, true);
-    }
-  };
-  return commitAction;
+  return (
+    <Icon
+      id = "PUSH"
+      title = {title ?? "Update FC"}
+      size = {14}
+      onClick = {() => {
+        onChanged(value, error, true);
+      }}
+    />
+  );
 }
 
 /** @category Form Fields Actions*/
-function Actions(actions?: ActionsProps[]): JSX.Element | null {
-  if(!actions) return null;
+export function Actions(props?: Children): JSX.Element | null {
+  if(!props) return null;
+  const { children } = props;
   const cssFieldAction = Utils.classes(
     'dome-xForm-field-actions'
   );
-  const actionsHtml = actions?.map((p, index) =>
-    <Icon
-      key={"action_"+index.toString()}
-      {...p}
-    />
-  );
+
   return (
     <div className={cssFieldAction}>
-      {actionsHtml}
+      {children}
     </div>
   );
 }
@@ -923,7 +925,7 @@ export interface GenericFieldProps extends FilterProps, Children {
   /** Error (if any). */
   error?: FieldError;
   /** list of actions. */
-  actions?: ActionsProps[];
+  actions?: JSX.Element;
 }
 
 let FIELDID = 0;
@@ -939,7 +941,7 @@ export function useHtmlFor(): string {
    @category Form Fields
  */
 export function Field(props: GenericFieldProps): JSX.Element | null {
-  const { hidden, disabled, mode } = useContext(props);
+  const { hidden, disabled, layout } = useContext(props);
   if (hidden) return null;
 
   const { label, title, offset, htmlFor, actions, children } = props;
@@ -960,7 +962,6 @@ export function Field(props: GenericFieldProps): JSX.Element | null {
     <Warning offset={offset} warning={onError} error={error} />
   ) : null;
 
-  const actionsComponent = Actions(actions);
   const labelField: JSX.Element =  (
     <Label
       className={cssLabel}
@@ -971,15 +972,15 @@ export function Field(props: GenericFieldProps): JSX.Element | null {
     />
   );
 
-  switch(mode) {
-    case "block": return (
+  switch(layout) {
+    case "sidebar": return (
       <Hbox className={Utils.classes(
         "dome-xForm-field-block",
         disabled ? 'dome-disabled' : ""
       )}>
         <div className='dome-xForm-label-actions'>
           {labelField}
-          {actionsComponent}
+          {actions}
         </div>
         <div className={cssField} title={title}>
           {children}
@@ -987,7 +988,7 @@ export function Field(props: GenericFieldProps): JSX.Element | null {
         </div>
       </Hbox>
     );
-    case "grid":
+    case "page":
     default:
       return (
         <>
@@ -995,7 +996,7 @@ export function Field(props: GenericFieldProps): JSX.Element | null {
           <div className={cssField} title={title}>
             {children}
             {WARNING}
-            {actionsComponent}
+            {actions}
           </div>
         </>
       );
@@ -1019,7 +1020,7 @@ export interface FieldProps<A> extends FilterProps {
   /** Alternative error message (in case of error). */
   onError?: string;
   /** list of actions. */
-  actions?: ActionsProps[];
+  actions?: JSX.Element;
 }
 
 type InputEvent = { target: { value: string } };
diff --git a/ivette/src/dome/renderer/layout/style.css b/ivette/src/dome/renderer/layout/style.css
index f5575d649ff..23b0be38db0 100644
--- a/ivette/src/dome/renderer/layout/style.css
+++ b/ivette/src/dome/renderer/layout/style.css
@@ -262,7 +262,7 @@
 /* -------------------------------------------------------------------------- */
 
 /* Main Form */
-.dome-xForm-grid
+.dome-xForm-grid:not(.message-search)
 {
     flex: 1 1 auto;
     overflow: auto;
@@ -299,6 +299,57 @@
     justify-self: start ;
 }
 
+
+/* Block Container */
+.dome-xForm-sidebar
+{
+    padding: 4px 4px 4px 12px;
+
+    .dome-xForm-field-block,
+    .dome-xForm-field-block .dome-xForm-label-actions,
+    .dome-xForm-field-block > .dome-xForm-field {
+        display:flex;
+        flex-wrap: wrap;
+    }
+
+    .dome-xForm-field-block .dome-xForm-label{
+        margin-right: 15px;
+    }
+
+    .dome-xForm-field-block .dome-xForm-field-actions {
+        margin: auto 0;
+    }
+
+    .dome-xForm-field-block,
+    .dome-xForm-field-block > .dome-xForm-field {
+        max-width: 100%;
+        align-items: center;
+        margin: 2px 0;
+    }
+    .dome-xForm-field-block {
+        justify-content: space-between;
+        margin-top: 4px;
+    }
+    .dome-xForm-section {
+        left: auto; /* Cancel dome left placement */
+    }
+    .dome-xForm-section:not(:first-child) {
+        margin-top: 15px ;
+    }
+    .dome-xForm-field-block {
+        position:relative;
+        border-radius: 4px;
+        background-color: var(--background-block-form);
+        padding: 2px 8px 2px 2px;
+    }
+    .dome-xForm-label {
+        font-weight: bold;
+    }
+    .message-emitter-category {
+        margin-bottom: 10px;
+    }
+}
+
 /* -------------------------------------------------------------------------- */
 /* --- Fields                                                             --- */
 /* -------------------------------------------------------------------------- */
@@ -364,37 +415,6 @@
     opacity: 0.9 ;
 }
 
-/* -------------------------------------------------------------------------- */
-/* --- Field  block                                                       --- */
-/* -------------------------------------------------------------------------- */
-
-.dome-xForm-field-block,
-.dome-xForm-field-block .dome-xForm-label-actions,
-.dome-xForm-field-block > .dome-xForm-field {
-    display:flex;
-    flex-wrap: wrap;
-}
-
-.dome-xForm-field-block .dome-xForm-label{
-    margin-right: 15px;
-}
-
-.dome-xForm-field-block .dome-xForm-field-actions {
-    margin: auto 0;
-}
-
-.dome-xForm-field-block,
-.dome-xForm-field-block > .dome-xForm-field {
-    max-width: 100%;
-    align-items: center;
-    margin: 2px 0;
-}
-
-.dome-xForm-field-block {
-    justify-content: space-between;
-    margin-top: 4px;
-}
-
 /* -------------------------------------------------------------------------- */
 /* --- Errors                                                             --- */
 /* -------------------------------------------------------------------------- */
diff --git a/ivette/src/dome/renderer/light.css b/ivette/src/dome/renderer/light.css
index 55a5cf4c0d5..e9f6bd28b85 100644
--- a/ivette/src/dome/renderer/light.css
+++ b/ivette/src/dome/renderer/light.css
@@ -32,6 +32,7 @@
     --background-alterning-even: #efefef;
     --background-interaction: white;
     --background-report: white;
+    --background-block-form: #dadada;
 
     --selected-element: #8ce0fb;
     --checked-element: #54abef;
diff --git a/ivette/src/renderer/Messages.tsx b/ivette/src/renderer/Messages.tsx
index 7d306012b9a..7d92ff46d9c 100644
--- a/ivette/src/renderer/Messages.tsx
+++ b/ivette/src/renderer/Messages.tsx
@@ -253,7 +253,7 @@ function MessageFilter(props: { filter: State<Filter> }): JSX.Element {
     ));
 
   return (
-    <Forms.Page className="message-search">
+    <Forms.Form className="message-search">
       <Forms.CheckboxField
         label="Current function"
         title="Only show messages emitted at the current function"
@@ -290,7 +290,7 @@ function MessageFilter(props: { filter: State<Filter> }): JSX.Element {
           <Forms.CheckboxField label='Others' state={othersState} />
         </div>
       </Section>
-    </Forms.Page>
+    </Forms.Form>
   );
 }
 
diff --git a/ivette/src/renderer/Preferences.tsx b/ivette/src/renderer/Preferences.tsx
index d8b6a5649ec..9bbd485ae13 100644
--- a/ivette/src/renderer/Preferences.tsx
+++ b/ivette/src/renderer/Preferences.tsx
@@ -137,7 +137,7 @@ function NotificationFields(): JSX.Element {
 
 export default function Preferences(): JSX.Element {
   return (
-    <Forms.Page>
+    <Forms.Form>
       <Forms.Section label="Theme" unfold>
         <ThemeFields />
       </Forms.Section>
@@ -148,7 +148,7 @@ export default function Preferences(): JSX.Element {
         <ConsoleFields />
         <NotificationFields />
       </Forms.Section>
-    </Forms.Page>
+    </Forms.Form>
   );
 }
 
-- 
GitLab