Skip to content

[In Progress] [Feat] Table Lite Component #1939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { CellProps } from "components/table/EditableCell";
import { DateTimeComp } from "comps/comps/tableComp/column/columnTypeComps/columnDateTimeComp";
import { TimeComp } from "./columnTypeComps/columnTimeComp";
import { ButtonComp } from "comps/comps/tableComp/column/simpleColumnTypeComps";
import { withType } from "comps/generators";
import { trans } from "i18n";
import { Dropdown } from "lowcoder-design/src/components/Dropdown";
import { BooleanComp } from "./columnTypeComps/columnBooleanComp";
import { SwitchComp } from "./columnTypeComps/columnSwitchComp";
import { DateComp } from "./columnTypeComps/columnDateComp";
import { ImageComp } from "./columnTypeComps/columnImgComp";
import { LinkComp } from "./columnTypeComps/columnLinkComp";
import { ColumnLinksComp } from "./columnTypeComps/columnLinksComp";
import { ColumnMarkdownComp } from "./columnTypeComps/columnMarkdownComp";
import { ProgressComp } from "./columnTypeComps/columnProgressComp";
import { RatingComp } from "./columnTypeComps/columnRatingComp";
import { BadgeStatusComp } from "./columnTypeComps/columnStatusComp";
import { ColumnTagsComp } from "./columnTypeComps/columnTagsComp";
import { ColumnSelectComp } from "./columnTypeComps/columnSelectComp";
import { SimpleTextComp } from "./columnTypeComps/simpleTextComp";
import { ColumnNumberComp } from "./columnTypeComps/ColumnNumberComp";

import { ColumnAvatarsComp } from "./columnTypeComps/columnAvatarsComp";
import { ColumnDropdownComp } from "./columnTypeComps/columnDropdownComp";

const actionOptions = [
{
label: trans("table.avatars"),
value: "avatars",
},
{
label: trans("table.text"),
value: "text",
},
{
label: trans("table.number"),
value: "number",
},
{
label: trans("table.link"),
value: "link",
},
{
label: trans("table.links"),
value: "links",
},
{
label: trans("table.tag"),
value: "tag",
},
{
label: trans("table.select"),
value: "select",
},
{
label: trans("table.dropdown"),
value: "dropdown",
},
{
label: trans("table.badgeStatus"),
value: "badgeStatus",
},
{
label: trans("table.button"),
value: "button",
},
{
label: trans("table.image"),
value: "image",
},
{
label: trans("table.time"),
value: "time",
},

{
label: trans("table.date"),
value: "date",
},
{
label: trans("table.dateTime"),
value: "dateTime",
},
{
label: "Markdown",
value: "markdown",
},
{
label: trans("table.boolean"),
value: "boolean",
},
{
label: trans("table.switch"),
value: "switch",
},
{
label: trans("table.rating"),
value: "rating",
},
{
label: trans("table.progress"),
value: "progress",
},
] as const;

export const ColumnTypeCompMap = {
avatars: ColumnAvatarsComp,
text: SimpleTextComp,
number: ColumnNumberComp,
button: ButtonComp,
badgeStatus: BadgeStatusComp,
link: LinkComp,
tag: ColumnTagsComp,
select: ColumnSelectComp,
dropdown: ColumnDropdownComp,
links: ColumnLinksComp,
image: ImageComp,
markdown: ColumnMarkdownComp,
dateTime: DateTimeComp,
boolean: BooleanComp,
switch: SwitchComp,
rating: RatingComp,
progress: ProgressComp,
date: DateComp,
time: TimeComp,
};

type ColumnTypeMapType = typeof ColumnTypeCompMap;
export type ColumnTypeKeys = keyof ColumnTypeMapType;

const TypedColumnTypeComp = withType(ColumnTypeCompMap, "text");

export class ColumnTypeComp extends TypedColumnTypeComp {
override getView() {
const childView = this.children.comp.getView();
return {
view: (cellProps: CellProps) => {
return childView(cellProps);
},
value: this.children.comp.getDisplayValue(),
};
}

private handleTypeChange: (value: ColumnTypeKeys) => void = (value) => {
// Keep the previous text value, some components do not have text, the default value is currentCell
let textRawData = "{{currentCell}}";
if (this.children.comp.children.hasOwnProperty("text")) {
textRawData = (this.children.comp.children as any).text.toJsonValue();
}
this.dispatchChangeValueAction({
compType: value,
comp: { text: textRawData },
} as any);
}

override getPropertyView() {
return (
<>
<Dropdown
showSearch={true}
value={this.children.compType.getView()}
options={actionOptions}
label={trans("table.columnType")}
onChange={this.handleTypeChange}
/>
{this.children.comp.getPropertyView()}
</>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import {
CellViewReturn,
EditableCell,
EditViewFn,
TABLE_EDITABLE_SWITCH_ON,
} from "components/table/EditableCell";
import { stateComp } from "comps/generators";
import {
MultiCompBuilder,
PropertyViewFnTypeForComp,
ToConstructor,
ViewFnTypeForComp,
} from "comps/generators/multi";
import _ from "lodash";
import {
CompConstructor,
ConstructorToNodeType,
fromRecord,
NodeToValue,
RecordConstructorToComp,
withFunction,
} from "lowcoder-core";
import { ReactNode } from "react";
import { JSONValue } from "util/jsonTypes";

export const __COLUMN_DISPLAY_VALUE_FN = "__COLUMN_DISPLAY_VALUE_FN";

type RecordConstructorToNodeValue<T> = {
[K in keyof T]: NodeToValue<ConstructorToNodeType<T[K]>>;
};

type ViewValueFnType<ChildrenCtorMap extends Record<string, CompConstructor<unknown>>> = (
nodeValue: RecordConstructorToNodeValue<ChildrenCtorMap>
) => JSONValue;

type NewChildrenCtorMap<ChildrenCtorMap, T extends JSONValue> = ChildrenCtorMap & {
changeValue: ReturnType<typeof stateComp<T | null>>;
};

export type ColumnTypeViewFn<ChildrenCtroMap, T extends JSONValue, ViewReturn> = ViewFnTypeForComp<
ViewReturn,
RecordConstructorToComp<NewChildrenCtorMap<ChildrenCtroMap, T>>
>;

export class ColumnTypeCompBuilder<
ChildrenCtorMap extends Record<string, CompConstructor<unknown>>,
T extends JSONValue = JSONValue
> {
private childrenMap: NewChildrenCtorMap<ChildrenCtorMap, T>;
private propertyViewFn?: PropertyViewFnTypeForComp<
RecordConstructorToComp<NewChildrenCtorMap<ChildrenCtorMap, T>>
>;
private stylePropertyViewFn?: PropertyViewFnTypeForComp<
RecordConstructorToComp<NewChildrenCtorMap<ChildrenCtorMap, T>>
>;
private editViewFn?: EditViewFn<T>;
private cleanupFunctions: (() => void)[] = [];

constructor(
childrenMap: ChildrenCtorMap,
private viewFn: ColumnTypeViewFn<ChildrenCtorMap, T, ReactNode>,
private displayValueFn: ViewValueFnType<ChildrenCtorMap>,
private baseValueFn?: ColumnTypeViewFn<ChildrenCtorMap, T, T>
) {
this.childrenMap = { ...childrenMap, changeValue: stateComp<T | null>(null) };
}

setEditViewFn(editViewFn: NonNullable<typeof this.editViewFn>) {
if (TABLE_EDITABLE_SWITCH_ON) {
this.editViewFn = editViewFn;
}
return this;
}

setPropertyViewFn(
propertyViewFn: PropertyViewFnTypeForComp<
RecordConstructorToComp<NewChildrenCtorMap<ChildrenCtorMap, T>>
>
) {
this.propertyViewFn = propertyViewFn;
return this;
}

setStylePropertyViewFn(
stylePropertyViewFn: PropertyViewFnTypeForComp<
RecordConstructorToComp<NewChildrenCtorMap<ChildrenCtorMap, T>>
>
) {
this.stylePropertyViewFn = stylePropertyViewFn;
return this;
}

build() {
if (!this.propertyViewFn) {
throw new Error("need property view fn");
}

// Memoize the props processing
const memoizedViewFn = _.memoize(
(props: any, dispatch: any) => {
const baseValue = this.baseValueFn?.(props, dispatch);
const normalView = this.viewFn(props, dispatch);
return (
<EditableCell<T>
{...props}
normalView={normalView}
dispatch={dispatch}
baseValue={baseValue}
changeValue={props.changeValue as any}
editViewFn={this.editViewFn}
/>
);
},
(props) => {
let safeOptions = [];
let safeAvatars = [];
if(props.options) {
safeOptions = props.options.map((option: Record<string, any>) => {
const {prefixIcon, suffixIcon, ...safeOption} = option;
return safeOption;
})
}
if(props.avatars) {
safeAvatars = props.avatars.map((avatar: Record<string, any>) => {
const {AvatarIcon, ...safeAvatar} = avatar;
return safeAvatar;
})
}
const {
prefixIcon,
suffixIcon,
iconFalse,
iconTrue,
iconNull,
tagColors,
options,
avatars,
...safeProps
} = props;
return safeProps;
}
);

const viewFn: ColumnTypeViewFn<ChildrenCtorMap, T, CellViewReturn> =
(props, dispatch): CellViewReturn =>
(cellProps) => memoizedViewFn({ ...props, ...cellProps } as any, dispatch);

const ColumnTypeCompTmp = new MultiCompBuilder(
this.childrenMap as ToConstructor<
RecordConstructorToComp<NewChildrenCtorMap<ChildrenCtorMap, T>>
>,
viewFn
)
.setPropertyViewFn(this.propertyViewFn)
.build();

const displayValueFn = this.displayValueFn;
const editViewFn = this.editViewFn;

return class extends ColumnTypeCompTmp {
// table cell data
private _displayValue: JSONValue = null;
private cleanupFunctions: (() => void)[] = [];
constructor(props: any) {
super(props);
this.cleanupFunctions.push(() => {
this._displayValue = null;
memoizedViewFn.cache.clear?.();
});
}

override extraNode() {
return {
node: {
[__COLUMN_DISPLAY_VALUE_FN]: withFunction(
fromRecord(this.childrenNode()),
() => displayValueFn
),
},
updateNodeFields: (value: any) => {
const displayValueFunc = value[__COLUMN_DISPLAY_VALUE_FN];
this._displayValue = displayValueFunc(value);
return { displayValue: this._displayValue };
},
};
}

/**
* Get the data actually displayed by the table cell
*/
getDisplayValue() {
return this._displayValue;
}

static canBeEditable() {
return !_.isNil(editViewFn);
}

componentWillUnmount() {
// Cleanup all registered cleanup functions
this.cleanupFunctions.forEach(cleanup => cleanup());
this.cleanupFunctions = [];
}
};
}

// Cleanup method to be called when the builder is no longer needed
cleanup() {
this.cleanupFunctions.forEach(cleanup => cleanup());
this.cleanupFunctions = [];
}
}
Loading
Loading