Skip to content

Commit

Permalink
initial generic Lines
Browse files Browse the repository at this point in the history
  • Loading branch information
olmobrutall committed Feb 15, 2024
1 parent 3d4cb33 commit 4fcdba3
Show file tree
Hide file tree
Showing 62 changed files with 1,131 additions and 991 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public static ResetPasswordRequestEntity ResetPasswordRequest(UserEntity user)

return new ResetPasswordRequestEntity
{
Code = MyRandom.Current.NextString(32),
Code = Random.Shared.NextString(32),
User = user,
RequestDate = Clock.Now,
}.Save();
Expand Down
2 changes: 1 addition & 1 deletion Extensions/Signum.Authorization/AuthAdminClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export function queryIsFindable(queryKey: string, fullScreen: boolean) {
return allowed == "Allow" || allowed == "EmbeddedOnly" && !fullScreen;
}

export function taskAuthorizeProperties(lineBase: LineBaseController<LineBaseProps>, state: LineBaseProps) {
export function taskAuthorizeProperties(lineBase: LineBaseController<LineBaseProps, unknown>, state: LineBaseProps) {
if (state.ctx.propertyRoute &&
state.ctx.propertyRoute.propertyRouteType == "Field") {

Expand Down
23 changes: 11 additions & 12 deletions Extensions/Signum.Files/Components/FileImageLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@ import { FormGroup } from '@framework/Lines/FormGroup'
import * as Services from '@framework/Services'
import { ModifiableEntity, Lite, Entity, isLite, isEntity } from '@framework/Signum.Entities'
import { IFile, FileTypeSymbol } from '../Signum.Files'
import { EntityBaseProps, EntityBaseController } from '@framework/Lines/EntityBase'
import { EntityBaseProps, EntityBaseController, Aprox, AsEntity } from '@framework/Lines/EntityBase'
import { FileDownloaderConfiguration } from './FileDownloader'
import { FileUploader } from './FileUploader'
import { FileImage } from './FileImage';
import "./Files.css"
import { FetchAndRemember } from '@framework/Lines'
import { useController } from '@framework/Lines/LineBase'
import { genericForwardRef, useController } from '@framework/Lines/LineBase'
import { ImageModal } from './ImageModal'

export { FileTypeSymbol };

export interface FileImageLineProps extends EntityBaseProps {
ctx: TypeContext<ModifiableEntity & IFile | Lite<IFile & Entity> | undefined | null>;
export interface FileImageLineProps<V extends ModifiableEntity & IFile | Lite<IFile & Entity> | null> extends EntityBaseProps<V> {
dragAndDrop?: boolean;
dragAndDropMessage?: string;
fileType?: FileTypeSymbol;
Expand All @@ -29,9 +28,9 @@ export interface FileImageLineProps extends EntityBaseProps {
}


export class FileImageLineController extends EntityBaseController<FileImageLineProps> {
export class FileImageLineController<V extends ModifiableEntity & IFile | Lite<IFile & Entity> | null> extends EntityBaseController<FileImageLineProps<V>, V>{

getDefaultProps(state: FileImageLineProps) {
getDefaultProps(state: FileImageLineProps<V>) {

super.getDefaultProps(state);

Expand All @@ -49,13 +48,13 @@ export class FileImageLineController extends EntityBaseController<FileImageLineP
}
}

handleFileLoaded = (file: IFile & ModifiableEntity) =>{
this.convert(file)
handleFileLoaded = (file: IFile & ModifiableEntity) => {
this.convert(file as Aprox<V>)
.then(f => this.setValue(f));
}
}

export const FileImageLine = React.forwardRef(function FileImageLine(props: FileImageLineProps, ref: React.Ref<FileImageLineController>) {
export const FileImageLine = genericForwardRef(function FileImageLine<V extends ModifiableEntity & IFile | Lite<IFile & Entity> | null>(props: FileImageLineProps<V>, ref: React.Ref<FileImageLineController<V>>) {
const c = useController(FileImageLineController, props, ref);
const p = c.props;

Expand Down Expand Up @@ -94,7 +93,7 @@ export const FileImageLine = React.forwardRef(function FileImageLine(props: File
<FetchAndRemember lite={val! as Lite<IFile & Entity>}>{file => <FileImage file={file} style={{ maxWidth: "100px" }} onClick={e => ImageModal.show(file as IFile & ModifiableEntity, e)} {...p.imageHtmlAttributes} />}</FetchAndRemember> :
<FileImage file={val as IFile & ModifiableEntity} style={{ maxWidth: "100px" }} onClick={e => ImageModal.show(val as IFile & ModifiableEntity, e)} {...p.imageHtmlAttributes} ajaxOptions={p.ajaxOptions} />;

const removeButton = c.renderRemoveButton(true, val);
const removeButton = c.renderRemoveButton(true);

if (removeButton == null)
return content;
Expand All @@ -108,8 +107,8 @@ export const FileImageLine = React.forwardRef(function FileImageLine(props: File
}
});

FileImageLine.defaultProps = {
(FileImageLine as any).defaultProps = {
accept: "image/*",
dragAndDrop: true
};
} as FileImageLineProps<any>;

25 changes: 12 additions & 13 deletions Extensions/Signum.Files/Components/FileLine.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
import * as React from 'react'
import { classes } from '@framework/Globals'
import { TypeContext } from '@framework/TypeContext'
import { getSymbol } from '@framework/Reflection'
import { Type, getSymbol } from '@framework/Reflection'
import { FormGroup } from '@framework/Lines/FormGroup'
import { ModifiableEntity, Lite, Entity, getToString, } from '@framework/Signum.Entities'
import { IFile, FileTypeSymbol } from '../Signum.Files'
import { EntityBaseProps, EntityBaseController } from '@framework/Lines/EntityBase'
import { EntityBaseProps, EntityBaseController, AsEntity, Aprox } from '@framework/Lines/EntityBase'
import { FileDownloader, FileDownloaderConfiguration, DownloadBehaviour } from './FileDownloader'
import { FileUploader } from './FileUploader'

import "./Files.css"
import { useController } from '@framework/Lines/LineBase'
import { genericForwardRef, useController } from '@framework/Lines/LineBase'

export { FileTypeSymbol };

export interface FileLineProps extends EntityBaseProps {
ctx: TypeContext<ModifiableEntity & IFile | Lite<IFile & Entity> | undefined | null>;
export interface FileLineProps<V extends ModifiableEntity & IFile | Lite<IFile & Entity> | null> extends EntityBaseProps<V> {
download?: DownloadBehaviour;
showFileIcon?: boolean;
dragAndDrop?: boolean;
dragAndDropMessage?: string;
fileType?: FileTypeSymbol;
accept?: string;
configuration?: FileDownloaderConfiguration<IFile>;
configuration?: FileDownloaderConfiguration<AsEntity<V>>;
maxSizeInBytes?: number;
}


export class FileLineController extends EntityBaseController<FileLineProps>{
export class FileLineController<V extends ModifiableEntity & IFile | Lite<IFile & Entity> | null> extends EntityBaseController<FileLineProps<V>, V>{

getDefaultProps(state: FileLineProps) {
getDefaultProps(state: FileLineProps<V>) {

super.getDefaultProps(state);

Expand All @@ -50,12 +49,12 @@ export class FileLineController extends EntityBaseController<FileLineProps>{

handleFileLoaded = (file: IFile & ModifiableEntity) => {

this.convert(file)
this.convert(file as Aprox<V>)
.then(f => this.setValue(f));
}
}

export const FileLine = React.memo(React.forwardRef(function FileLine(props: FileLineProps, ref: React.Ref<FileLineController>) {
export const FileLine = React.memo(genericForwardRef(function FileLine<V extends ModifiableEntity & IFile | Lite<IFile & Entity> | null>(props: FileLineProps<V>, ref: React.Ref<FileLineController<V>>) {
const c = useController(FileLineController, props, ref);
const p = c.props;

Expand Down Expand Up @@ -95,7 +94,7 @@ export const FileLine = React.memo(React.forwardRef(function FileLine(props: Fil
const content = p.download == "None" ?
<span className={classes(ctx.formControlClass, "file-control")} > {getToString(val)}</span > :
<FileDownloader
configuration={p.configuration}
configuration={p.configuration as FileDownloaderConfiguration<IFile>}
download={p.download}
showFileIcon={p.showFileIcon}
entityOrLite={val}
Expand All @@ -104,7 +103,7 @@ export const FileLine = React.memo(React.forwardRef(function FileLine(props: Fil
const buttons =
<>
{c.props.extraButtonsBefore && c.props.extraButtonsBefore(c)}
{c.renderRemoveButton(true, val)}
{c.renderRemoveButton(true)}
{c.props.extraButtons && c.props.extraButtons(c)}
</>;

Expand All @@ -124,4 +123,4 @@ export const FileLine = React.memo(React.forwardRef(function FileLine(props: Fil
download: "ViewOrSave",
dragAndDrop: true,
showFileIcon: true
} as FileLineProps;
} as FileLineProps<any>;
35 changes: 15 additions & 20 deletions Extensions/Signum.Files/Components/MultiFileImageLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'
import { classes } from '@framework/Globals'
import * as Constructor from '@framework/Constructor'
import { TypeContext } from '@framework/TypeContext'
import { getSymbol } from '@framework/Reflection'
import { Type, getSymbol } from '@framework/Reflection'
import { FormGroup } from '@framework/Lines/FormGroup'
import { ModifiableEntity, Lite, Entity, MList, SearchMessage, EmbeddedEntity, EntityControlMessage, getToString } from '@framework/Signum.Entities'
import { IFile, FileTypeSymbol } from '../Signum.Files'
Expand All @@ -14,13 +14,13 @@ import "./Files.css"
import { EntityListBaseController, EntityListBaseProps } from '@framework/Lines/EntityListBase'
import { FetchAndRemember } from '@framework/Lines'
import { FileImage } from './FileImage';
import { useController } from '@framework/Lines/LineBase'
import { genericForwardRef, useController } from '@framework/Lines/LineBase'
import { ImageModal } from './ImageModal'
import { Aprox, AsEntity } from '@framework/Lines/EntityBase'

export { FileTypeSymbol };

interface MultiFileImageLineProps extends EntityListBaseProps {
ctx: TypeContext<MList<ModifiableEntity & IFile | Lite<IFile & Entity> | EmbeddedEntity>>;
interface MultiFileImageLineProps<V extends ModifiableEntity/* & IFile*/ | Lite</*IFile & */Entity>> extends EntityListBaseProps<V> {
download?: DownloadBehaviour;
dragAndDrop?: boolean;
dragAndDropMessage?: string;
Expand All @@ -29,13 +29,13 @@ interface MultiFileImageLineProps extends EntityListBaseProps {
configuration?: FileDownloaderConfiguration<IFile>;
imageHtmlAttributes?: React.ImgHTMLAttributes<HTMLImageElement>;
maxSizeInBytes?: number;
getFile?: (ectx: EmbeddedEntity) => ModifiableEntity & IFile | Lite<IFile & Entity>;
createEmbedded?: (file: ModifiableEntity & IFile) => Promise<EmbeddedEntity>;
getFile?: (ectx: V) => ModifiableEntity & IFile | Lite<IFile & Entity>;
createEmbedded?: (file: ModifiableEntity & IFile | Lite<IFile & Entity>) => Promise<V>;
}

export class MultiFileImageLineController extends EntityListBaseController<MultiFileImageLineProps> {
export class MultiFileImageLineController<V extends ModifiableEntity /*& IFile*/ | Lite</*IFile & */Entity>> extends EntityListBaseController<MultiFileImageLineProps<V>, V> {

overrideProps(p: MultiFileImageLineProps, overridenProps: MultiFileImageLineProps) {
overrideProps(p: MultiFileImageLineProps<V>, overridenProps: MultiFileImageLineProps<V>) {
super.overrideProps(p, overridenProps);

let pr = p.ctx.propertyRoute;
Expand Down Expand Up @@ -68,18 +68,14 @@ export class MultiFileImageLineController extends EntityListBaseController<Multi
this.props.createEmbedded(file)
.then(em => em && this.addElement(em));
else
this.convert(file)
this.convert(file as unknown as Aprox<V>)
.then(f => this.addElement(f));
}

defaultCreate() {
return Constructor.construct(this.props.type!.name);
}
}

export const MultiFileImageLine = React.forwardRef(function MultiFileLine(props: MultiFileImageLineProps, ref: React.Ref<MultiFileImageLineController>) {
export const MultiFileImageLine = genericForwardRef(function MultiFileLine<V extends ModifiableEntity /*& IFile*/ | Lite</*IFile &*/ Entity>>(props: MultiFileImageLineProps<V>, ref: React.Ref<MultiFileImageLineController<V>>) {

const c = useController(MultiFileImageLineController, props, ref);
const c = useController(MultiFileImageLineController<V>, props, ref);
const p = c.props;

if (c.isHidden)
Expand All @@ -95,9 +91,9 @@ export const MultiFileImageLine = React.forwardRef(function MultiFileLine(props:
{
c.getMListItemContext(p.ctx.subCtx({ formGroupStyle: "None" })).map(mlec =>
<div className="sf-file-image-container m-2" key={mlec.index}>
{p.getComponent ? p.getComponent(mlec) :
{p.getComponent ? p.getComponent(mlec as TypeContext<AsEntity<V>>) :
p.download == "None" ? <span className={classes(mlec.formControlClass, "file-control")} > {getToString(mlec.value)}</span > :
renderFile(p.getFile ? (mlec as TypeContext<EmbeddedEntity>).subCtx(p.getFile) : mlec as TypeContext<ModifiableEntity & IFile | Lite<IFile & Entity> | undefined | null>)}
renderFile(p.getFile ? mlec.subCtx(p.getFile) : mlec as unknown as TypeContext<ModifiableEntity & IFile | Lite<IFile & Entity>>)}
{!p.ctx.readOnly &&
<a href="#" title={EntityControlMessage.Remove.niceToString()}
className="sf-line-button sf-remove"
Expand Down Expand Up @@ -129,20 +125,19 @@ export const MultiFileImageLine = React.forwardRef(function MultiFileLine(props:
);


function renderFile(ctx: TypeContext<ModifiableEntity & IFile | Lite<IFile & Entity> | undefined | null>) {
function renderFile(ctx: TypeContext<ModifiableEntity & IFile | Lite<IFile & Entity>>) {
const val = ctx.value!;

return ctx.propertyRoute!.typeReference().isLite ?
<FetchAndRemember lite={val! as Lite<IFile & Entity>}>{file => <FileImage file={file} {...p.imageHtmlAttributes} style={{ maxWidth: "100px" }} />}</FetchAndRemember> :
<FileImage file={val as IFile & ModifiableEntity} {...p.imageHtmlAttributes} style={{ maxWidth: "100px" }} onClick={e => ImageModal.show(val as IFile & ModifiableEntity, e)} />;
}

});

(MultiFileImageLine as any).defaultProps = {
download: "SaveAs",
dragAndDrop: true
} as MultiFileImageLineProps;
} as MultiFileImageLineProps<any>;



32 changes: 14 additions & 18 deletions Extensions/Signum.Files/Components/MultiFileLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'
import { classes } from '@framework/Globals'
import * as Constructor from '@framework/Constructor'
import { ButtonBarElement, TypeContext } from '@framework/TypeContext'
import { getSymbol } from '@framework/Reflection'
import { Type, getSymbol } from '@framework/Reflection'
import { FormGroup } from '@framework/Lines/FormGroup'
import { ModifiableEntity, Lite, Entity, MList, SearchMessage, EntityControlMessage, EmbeddedEntity, MListElement, getToString } from '@framework/Signum.Entities'
import { IFile, FileTypeSymbol } from '../Signum.Files'
Expand All @@ -11,13 +11,13 @@ import { FileUploader } from './FileUploader'
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import "./Files.css"
import { EntityListBaseController, EntityListBaseProps } from '@framework/Lines/EntityListBase'
import { useController } from '@framework/Lines/LineBase'
import { genericForwardRef, useController } from '@framework/Lines/LineBase'
import { EntityBaseController } from '@framework/Lines'
import { Aprox, AsEntity } from '@framework/Lines/EntityBase'

export { FileTypeSymbol };

interface MultiFileLineProps extends EntityListBaseProps {
ctx: TypeContext<MList<ModifiableEntity & IFile | Lite<IFile & Entity> | ModifiableEntity /*implement getFile create Embedded*/>>;
interface MultiFileLineProps<V extends ModifiableEntity/* & IFile*/ | Lite</*IFile & */Entity>> extends EntityListBaseProps<V> {
download?: DownloadBehaviour;
showFileIcon?: boolean;
dragAndDrop?: boolean;
Expand All @@ -26,13 +26,13 @@ interface MultiFileLineProps extends EntityListBaseProps {
accept?: string;
configuration?: FileDownloaderConfiguration<IFile>;
maxSizeInBytes?: number;
getFileFromElement?: (e: any /*ModifiableEntity*/) => ModifiableEntity & IFile | Lite<IFile & Entity>;
createElementFromFile?: (file: ModifiableEntity & IFile) => Promise<ModifiableEntity | undefined>;
getFileFromElement?: (ectx: V) => ModifiableEntity & IFile | Lite<IFile & Entity>;
createElementFromFile?: (file: ModifiableEntity & IFile) => Promise<V>;
}

export class MultiFileLineController extends EntityListBaseController<MultiFileLineProps> {
export class MultiFileLineController<V extends ModifiableEntity /*& IFile*/ | Lite</*IFile & */Entity>> extends EntityListBaseController<MultiFileLineProps<V>, V> {

overrideProps(p: MultiFileLineProps, overridenProps: MultiFileLineProps) {
overrideProps(p: MultiFileLineProps<V>, overridenProps: MultiFileLineProps<V>) {

p.view = EntityBaseController.defaultIsViewable(p.type!, false) && overridenProps.getFileFromElement != null;

Expand Down Expand Up @@ -70,15 +70,11 @@ export class MultiFileLineController extends EntityListBaseController<MultiFileL
this.props.createElementFromFile(file)
.then(em => em && this.addElement(em));
else
this.convert(file)
this.convert(file as unknown as Aprox<V>)
.then(f => this.addElement(f));
}

defaultCreate() {
return Constructor.construct(this.props.type!.name);
}

renderElementViewButton(btn: boolean, entity: ModifiableEntity | Lite<Entity>, index: number) {
renderElementViewButton(btn: boolean, entity: V, index: number) {

if (!this.canView(entity))
return undefined;
Expand All @@ -94,7 +90,7 @@ export class MultiFileLineController extends EntityListBaseController<MultiFileL
}
}

export const MultiFileLine = React.forwardRef(function MultiFileLine(props: MultiFileLineProps, ref: React.Ref<MultiFileLineController>) {
export const MultiFileLine = genericForwardRef(function MultiFileLine<V extends ModifiableEntity /*& IFile*/ | Lite</*IFile &*/ Entity>>(props: MultiFileLineProps<V>, ref: React.Ref<MultiFileLineController<V>>) {
const c = useController(MultiFileLineController, props, ref);
const p = c.props;

Expand All @@ -120,7 +116,7 @@ export const MultiFileLine = React.forwardRef(function MultiFileLine(props: Mult
</a>}
</td>
<td style={{ width: "100%" }}>
{p.getComponent ? p.getComponent(mlec) :
{p.getComponent ? p.getComponent(mlec as TypeContext<AsEntity<V>>) :
p.download == "None" ?
<span className={classes(mlec.formControlClass, "file-control")} >
{getToString(p.getFileFromElement ? p.getFileFromElement(mlec.value) : mlec.value)}
Expand All @@ -130,7 +126,7 @@ export const MultiFileLine = React.forwardRef(function MultiFileLine(props: Mult
showFileIcon={p.showFileIcon}
download={p.download}
containerEntity={p.getFileFromElement ? mlec.value as ModifiableEntity : undefined}
entityOrLite={p.getFileFromElement ? p.getFileFromElement(mlec.value as ModifiableEntity) : mlec.value as ModifiableEntity & IFile | Lite<IFile & Entity>}
entityOrLite={p.getFileFromElement ? p.getFileFromElement(mlec.value) : mlec.value as ModifiableEntity & IFile | Lite<IFile & Entity>}
htmlAttributes={{ className: classes(mlec.formControlClass, "file-control") }} />
}
</td>
Expand Down Expand Up @@ -165,4 +161,4 @@ export const MultiFileLine = React.forwardRef(function MultiFileLine(props: Mult
download: "ViewOrSave",
showFileIcon: true,
dragAndDrop: true
} as MultiFileLineProps;
} as MultiFileLineProps<any>;
Loading

2 comments on commit 4fcdba3

@olmobrutall
Copy link
Collaborator Author

@olmobrutall olmobrutall commented on 4fcdba3 Feb 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EntityLine<T> with TS 5.4 beta

This Commit, (and this) finish a change that I wanted to do for a long time: Give a really stronly typed API for all the components inhering from:

  • LineBase (TextBoxLine, EnumLine...)
  • EntityBase (EntityLine, EntityCombo, EntityDetail...)
  • EntityListBase (EntityTable, EntityStrip, ...)
export interface LineBaseProps<V = unknown> extends StyleOptions {
  ctx: TypeContext<V>;
  ...
}


export interface EntityBaseProps<V extends ModifiableEntity | Lite<Entity> | null> extends LineBaseProps<V> {
  view?: boolean;
  viewOnCreate?: boolean;
  create?: boolean;
  createOnFind?: boolean;
  find?: boolean;
  remove?: boolean;
  paste?: boolean;

  onView?: (entity: NN<V>, pr: PropertyRoute) => Promise<Aprox<V> | undefined> | undefined;
  onCreate?: (pr: PropertyRoute) => Promise<Aprox<V> | undefined> | undefined;
  onFind?: () => Promise<Aprox<V> | undefined> | undefined;
  onRemove?: (entity: NN<V>) => Promise<boolean>;
  //...
  liteToString?: (e: AsEntity<V>) => string;

  getComponent?: (ctx: TypeContext<AsEntity<V>>) => React.ReactElement;
  getViewPromise?: (entity: AsEntity<V>) => undefined | string | Navigator.ViewPromise<ModifiableEntity>;

  fatLite?: boolean;
}

export interface EntityListBaseProps<V extends ModifiableEntity | Lite<Entity>> extends LineBaseProps<MList<V>> {
  view?: boolean | ((item: V) => boolean);
  viewOnCreate?: boolean;
  create?: boolean;
  createOnFind?: boolean;
  find?: boolean;
  remove?: boolean | ((item: V) => boolean);
  paste?: boolean;
  move?: boolean | ((item: V) => boolean);
  moveMode?: "DragIcon" | "MoveIcons";

  onView?: (entity: V, pr: PropertyRoute) => Promise<Aprox<V> | undefined> | undefined;
  onCreate?: (pr: PropertyRoute) => Promise<Aprox<V> | undefined> | undefined;
  onFindMany?: () => Promise<Aprox<V>[] | undefined> | undefined;
  onRemove?: (entity: V) => Promise<boolean>;
  //...

  liteToString?: (e: AsEntity<V>) => string;

  getComponent?: (ctx: TypeContext<AsEntity<V>>) => React.ReactElement;
  getViewPromise?: (entity: AsEntity<V>) => undefined | string | Navigator.ViewPromise<ModifiableEntity>;
  
  fatLite?: boolean;

  filterRows?: (ctxs: TypeContext<V>[]) => TypeContext<V>[]; /*Not only filter, also order, skip, take is supported*/
  onAddElement?: (list: MList<V>, newItem: V) => void,
}

This change should not affect you most of the time so you can still write:

interface InvoiceEntity {
   customer: CustomerEntity; 
}

<EntityLine ctx={ctx.subCtx(a=>a.customer} /> 

as usual, but when you want to use some of the events line onView or getComponent you get an strongly-typed lambda.

<EntityLine ctx={ctx.subCtx(a=>a.customer} onView={c => /*c is of type CustomerEntity now, not ModidiableEntity or Lite<Entity>*/}/> 

The generic parameter should be inferred ONLY from the ctx attribute. This was not possible before the new TypeScript 5.4 (currently in Beta) that introduces the new NoInfer<T> utility type.

Correctly expressing the behaviour of all the components events is a little bit challenging, for example imagine this example:

interface InvoiceEntity {
   customer: Lite<CustomerEntity>; //<--- A Lite<T> now! 
}

<EntityLine ctx={ctx.subCtx(a=>a.customer} onView={c => /*c is of type Lite<CustomerEntity> now!*/}/> 

if customer is a Lite<CustomerEntity>, onView won't retrieve it azutomatically for you, but in your lambda you can return either a CustomerEntity or Lite<CustomerEntity back to the EntityLine and it will be converted authomatically.

To express all this subtilities a few new typescript types are necessary:

//Example:   NN<Lite<CustomerEntity> | null>   => Lite<CustomerEntity>
export type NN<T> = NoInfer<NonNullable<T>>;

//Example:   Aprox<Lite<CustomerEntity> | null>   => Lite<CustomerEntity> | CustomerEntity
export type Aprox<T> = NoInfer<
  T extends Entity ? T | Lite<T> :
  T extends Lite<infer E> ? E | Lite<E> :
  T extends ModifiableEntity ? T :
  never>;

//Example AsEntity<Lite<CustomerEntity>>  => CustomerEntity
export type AsEntity<T> = NoInfer<
  T extends ModifiableEntity ? T :
  T extends Lite<infer E> ? E :
  never>;

//Example AsEntity<CustomerEntity>  => Lite<CustomerEntity>
export type AsLite<T> = NoInfer<
  T extends Entity ? Lite<T> :
  T extends Lite<infer E> ? T :
  never>;

All this utility types make extensive use of NoInfer, so that only ctx is used for the type inference.

Finally, one thing to note is tha EntityListBase does not inherit from EntityBase anymore, instead it inherits directly from LineBase for simplicity.

How to update:

Run the upgrade Upgrade_20240215_GenericLines. It will update to Typescrpt 5.4 beta and clean some code, like

 <EntityTable ctx={ctx.subCtx(a => a.props)} onChange={handleChange}
          columns={/*EntityTable.typedColumns<DynamicViewPropEmbedded>(   <---- UNECESSARY NOW! */[
            { property: a => a.name, template: sctx => <AutoLine ctx={sctx.subCtx(a => a.name)} onChange={handleChange} /> },
            { property: a => a.type, template: sctx => <AutoLine ctx={sctx.subCtx(a => a.type)} onChange={handleChange} /> },
          ]/*)*/} /
 <EntityDetail ctx={ctx} onChange={() => forceUpdate()} remove={false} getComponent={(ctx/*: TypeContext<WorkflowEventTaskConditionEval> UNECESSARY!*/) =>
      <div className="code-container">
        <pre style={{ border: "0px", margin: "0px" }}>{"public bool CustomCondition() \n{"}</pre>

Enjoy!

@MehdyKarimpour
Copy link
Contributor

@MehdyKarimpour MehdyKarimpour commented on 4fcdba3 Mar 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superb💯

Please sign in to comment.