diff --git a/ivette/src/dome/renderer/dark.css b/ivette/src/dome/renderer/dark.css index 66d7f12bb145f3b95b5b09331b0f73a36e6b962d..21ac1aabeceefdff15e96985a098bd2b39364289 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 0b3baaf2123aa594fdc0099c6b3e4c5507dbf6b0..3b1eb680774a8e81c64125aadf4889cf7d65e45f 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 f5575d649ffec949a11c2d9dcf142cfdb0384ab8..23b0be38db02ec76fa88e0af901325a84a2e471f 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 55a5cf4c0d591dccd0ef50fa77088d17cc8bf8e8..e9f6bd28b856556a1715004fd4ddbdb3a365bf8a 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 7d306012b9a0c1b5740cf094a6d48b3f81f80d68..7d92ff46d9cf9d0b6a31c3668db0cc99032e71a4 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 d8b6a5649ec0f5980f8fbaa4a81d18f592e13722..9bbd485ae13c74d5bce62cf59bb3ab61397097de 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> ); }