lodash#assign TypeScript Examples
The following examples show how to use
lodash#assign.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: tooltip.ts From S2 with MIT License | 6 votes |
mergeCellInfo = (cells: S2CellType[]): TooltipData[] => {
return map(cells, (stateCell) => {
const stateCellMeta = stateCell.getMeta();
return assign(
{},
stateCellMeta.query || {},
pick(stateCellMeta, ['colIndex', 'rowIndex']),
);
});
}
Example #2
Source File: schema.ts From am-editor with MIT License | 6 votes |
/**
* 过滤满足node节点规则的属性和样式
* @param node 节点,用于获取规则
* @param attributes 属性
* @param styles 样式
* @param apply 是否把过滤的属性和样式应用到节点上
* @returns
*/
filter(
node: NodeInterface,
attributes: { [k: string]: string },
styles: { [k: string]: string },
apply: boolean = false,
) {
const rule = this.getRule(node);
if (!rule) return;
const { globals } = this.data;
const globalRule = globals[rule.type] ? rule.type : undefined;
const allRule = Object.assign({}, rule, {
attributes: merge(
{},
rule.attributes,
globalRule ? globals[globalRule] : {},
),
});
this.filterAttributes(
attributes,
allRule,
apply ? (name) => node.removeAttributes(name) : undefined,
);
this.filterStyles(
styles,
allRule,
apply ? (name) => node.css(name, '') : undefined,
);
}
Example #3
Source File: dict.monad.ts From relate with GNU General Public License v3.0 | 6 votes |
/**
* Shallow merge of two Dicts (equivalent of Object.assign)
* ```ts
* const foo = Dict.from({foo: true, baz: {key1: 'foo'}});
* const bar = Dict.from({bar: 1, baz: {key2: 'bar'}});
* const fooBar = foo.assign(bar);
* fooBar.toObject() // {foo: true, bar: 1, baz: {key2: 'bar'}}
* ```
*/
assign<O extends Dict>(other: O): Dict<T & O> {
// @ts-ignore
return Dict.from(assign({}, this.toObject(), Dict.isDict(other) ? other.toObject() : other));
}
Example #4
Source File: Router.ts From mo360-ftk with MIT License | 6 votes |
public navigate(path: string, query?: RouteQuery): void {
path = path || '/';
if (!path.startsWith('/')) {
path = `/${path}`;
}
this.routes.forEach(item => {
const parser: RouteParser = new RouteParser(item.pattern);
const match: RouteParameter | boolean = parser.match(path);
if (match) {
this.route = assign({}, item, {
parameter: match,
query,
url: path + Url.buildQueryFromParameters(query as IUrlQueryMap),
});
}
});
}
Example #5
Source File: index.ts From fe-v5 with Apache License 2.0 | 6 votes |
update(newOptions: Options) {
/**
* 更新功能目前还没有想到最优的方案,暂时先根据目前的需求来强定制
*/
const options = assign({}, this.options, newOptions);
if (newOptions.series) {
options.series = newOptions.series;
}
this.options = options;
this.initSeries();
this.options.chartType === ChartType.Line ? this.initLine() : this.initStackArea();
this.initTooltip();
this.initScales();
// this.initScales(); // TODO: 更新偶尔会出现 colsePath 的情况,但是触发两次 initScales 就不会出现 closePath 情况
this.legend.updateOptions(options);
this.draw();
}
Example #6
Source File: utils.ts From prism-frontend with MIT License | 6 votes |
convertToTableData = (result: ExposedPopulationResult) => {
const {
key,
groupBy,
statistic,
featureCollection: { features },
} = result;
const fields = uniq(features.map(f => f.properties && f.properties[key]));
const featureProperties = features.map(feature => {
return {
[groupBy]: feature.properties?.[groupBy],
[key]: feature.properties?.[key],
[statistic]: feature.properties?.[statistic],
};
});
const rowData = mapValues(_groupBy(featureProperties, groupBy), k => {
return mapValues(_groupBy(k, key), v =>
parseInt(
v.map(x => x[statistic]).reduce((acc, value) => acc + value),
10,
),
);
});
const groupedRowData = Object.keys(rowData).map(k => {
return {
[groupBy]: k,
...rowData[k],
};
});
const groupedRowDataWithAllLabels = groupedRowData.map(row => {
const labelsWithoutValue = difference(fields, keysIn(row));
const extras = labelsWithoutValue.map(k => ({ [k]: 0 }));
return extras.length !== 0 ? assign(row, ...extras) : row;
});
const headlessRows = groupedRowDataWithAllLabels.map(row => {
// TODO - Switch between MAX and SUM depending on the polygon source.
// Then re-add "Total" to the list of columns
// const total = fields.map(f => row[f]).reduce((a, b) => a + b);
// return assign(row, { Total: total });
return row;
});
const columns = [groupBy, ...fields]; // 'Total'
const headRow = zipObject(columns, columns);
const rows = [headRow, ...headlessRows];
return { columns, rows };
}
Example #7
Source File: dbms.model.ts From relate with GNU General Public License v3.0 | 5 votes |
constructor(props: any) {
super(props);
// reassigning default values doesn't work when it's done from the parent class
assign(this, props);
}
Example #8
Source File: dict.monad.ts From relate with GNU General Public License v3.0 | 5 votes |
// eslint-disable-next-line @typescript-eslint/ban-types
assign<O extends object>(other: O): Dict<T & O>;
Example #9
Source File: project.model.ts From relate with GNU General Public License v3.0 | 5 votes |
constructor(props: any) {
super(props);
// reassigning default values doesn't work when it's done from the parent class
assign(this, props);
}
Example #10
Source File: project-install.model.ts From relate with GNU General Public License v3.0 | 5 votes |
constructor(props: any) {
super(props);
// reassigning default values doesn't work when it's done from the parent class
assign(this, props);
}
Example #11
Source File: model.abstract.ts From relate with GNU General Public License v3.0 | 5 votes |
constructor(props: T) {
assign(this, props);
this.validate();
}
Example #12
Source File: manifest.model.ts From relate with GNU General Public License v3.0 | 5 votes |
constructor(props: any) {
super(props);
// reassigning default values doesn't work when it's done from the parent class
assign(this, props);
}
Example #13
Source File: dbms-plugin.model.ts From relate with GNU General Public License v3.0 | 5 votes |
constructor(props: any) {
super(props);
// reassigning default values doesn't work when it's done from the parent class
assign(this, props);
}
Example #14
Source File: index.ts From fe-v5 with Apache License 2.0 | 5 votes |
initStackArea() {
this.chartContent = new StackArea(Object.assign(this.options, { series: this.baseSeries }), this.backContext);
}
Example #15
Source File: index.ts From fe-v5 with Apache License 2.0 | 5 votes |
initLine() {
// TODO: 涉及重复实例导
this.chartContent = new Line(Object.assign(this.options, { series: this.baseSeries }), this.backContext);
}
Example #16
Source File: GeneralSignup.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
export function GeneralSignup(props: GeneralSignupProps): React.ReactElement {
const [form] = Form.useForm();
const runtime = getRuntime();
const brand = runtime.getBrandSettings();
const enabledFeatures = runtime.getFeatureFlags();
const { t } = useTranslation(NS_GENERAL_AUTH);
const [, setForceUpdate] = useState<any>();
const passwordConfigMap = {
default: {
regex: /^.{6,20}$/,
description: "请输入6至20位密码",
},
strong: {
regex: /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^a-zA-Z0-9]).{8,20}$/,
description: "请输入8至20位密码,且同时包含大小写字母、数字、特殊字符",
},
backend: {},
};
let passwordLevel: keyof typeof passwordConfigMap = "default"; //特性开关
useEffect(() => {
if (enabledFeatures["enable-backend-password-config"]) {
(async () => {
passwordLevel = "backend";
passwordConfigMap[passwordLevel] =
await UserAdminApi_getPasswordConfig();
})();
}
}, []);
const MIN_USERNAME_LENGTH = 3; //特性开关
const MAX_USERNAME_LENGTH = 32; //特性开关
const usernamePattern = new RegExp(
`^[A-Za-z0-9][A-Za-z0-9|_\\-\\.]{${MIN_USERNAME_LENGTH - 1},${
MAX_USERNAME_LENGTH - 1
}}$`
);
const iniviteCodePattern = /^[0-9a-zA-Z]{9}$/;
const hideInvite = iniviteCodePattern.test(getInviteCode());
const [isCommonSignup, setIsCommonSignup] = useState(true);
const [isTermsVisible, setIsTermsVisible] = useState(false);
function showTerms(): void {
setIsTermsVisible(true);
}
function hideTerms(): void {
setIsTermsVisible(false);
}
function agreeTerms(): void {
form.setFieldsValue({
terms: true,
});
hideTerms();
}
function disagreeTerms(): void {
form.setFieldsValue({
terms: false,
});
hideTerms();
}
const [imageHeight, setImageHeight] = useState(window.innerHeight);
const onWindowResized = () => {
if (imageHeight < window.innerHeight) {
setImageHeight(window.innerHeight);
}
};
useEffect(() => {
const handleWindowResized = debounce(onWindowResized, 500, {
leading: false,
});
window.addEventListener("resize", handleWindowResized);
return () => {
window.removeEventListener("resize", handleWindowResized);
};
}, []);
const timer = useRef<any>();
const count = useRef<number>(duration);
const [verifyBtnDisabled, setVerifyBtnDisabled] = useState(true);
const [content, setContent] = useState(t(K.GET_VERIFY_CODE));
const [messageId, setMessageId] = useState("");
const handleVerifyBtnClick = async (
e: React.MouseEvent<HTMLElement, MouseEvent>
) => {
if (timer.current) return;
count.current -= 1;
setContent(t(K.GET_VERIFY_CODE_TIPS, { count: count.current }));
setVerifyBtnDisabled(true);
timer.current = setInterval(() => {
count.current -= 1;
setContent(t(K.GET_VERIFY_CODE_TIPS, { count: count.current }));
if (count.current === 0) {
clearInterval(timer.current);
timer.current = null;
count.current = duration;
setVerifyBtnDisabled(false);
setContent(t(K.GET_VERIFY_CODE));
}
}, 1000);
const result = await CustomerApi_sendApplicationVerificationCode({
phone_number: form.getFieldValue("phone"),
});
result.message_id && setMessageId(result.message_id);
};
const redirect = async (result: Record<string, any>): Promise<void> => {
runtime.reloadSharedData();
await runtime.reloadMicroApps();
resetLegacyIframe();
authenticate({
org: result.org,
username: result.username,
userInstanceId: result.userInstanceId,
accessRule: result.accessRule,
});
const { state } = getHistory().location;
const from =
state && state.from
? state.from
: {
pathname: "/",
};
const redirect = createLocation(from);
getHistory().push(redirect);
};
const onFinish = async (values: Record<string, any>): Promise<void> => {
values.password = encryptValue(values.password);
try {
let result: Record<string, any>;
if (isCommonSignup && !hideInvite) {
result = await OrgApi_saaSOrgRegister(
assign(omit(values, ["terms", "password2"]), {
message_id: messageId,
}) as OrgApi_SaaSOrgRegisterRequestBody
);
} else {
result = await AuthApi_register(
assign(
omit(values, ["terms", "password2", "username"]),
hideInvite
? { invite: getInviteCode(), name: values["username"] }
: { name: values["username"] }
) as AuthApi_RegisterRequestBody
);
}
if (result.loggedIn) {
redirect(result);
}
message.success(t(K.REGISTER_SUCCESS));
} catch (error) {
Modal.error({
title: t(K.REGISTER_FAILED),
content:
isCommonSignup && !hideInvite
? t(K.WRONG_VERIFICATION_CODE)
: t(K.WRONG_INVITE_CODE),
});
}
};
return (
<>
<div className={styles.signupWrapper}>
<div className={styles.signupHeader}>
<div className={styles.logoBar}>
<Link to="/">
{brand.auth_logo_url ? (
<img
src={brand.auth_logo_url}
style={{ height: 32, verticalAlign: "middle" }}
/>
) : (
<Logo height={32} style={{ verticalAlign: "middle" }} />
)}
</Link>
</div>
</div>
<div className={styles.signupImg}>
<img src={loginPng} style={{ height: imageHeight }} />
</div>
<div className={styles.signupForm}>
<Card bordered={false}>
{!hideInvite &&
(isCommonSignup ? (
<a
onClick={() => {
setIsCommonSignup(false);
}}
style={{ alignSelf: "flex-end" }}
id="JumpToJoinFormLink"
>
{t(K.JOIN_THE_ORGANIZATION)} <RightOutlined />
</a>
) : (
<a
onClick={() => {
setIsCommonSignup(true);
}}
id="JumpToCommonFormLink"
>
<LeftOutlined /> {t(K.REGISTER_COMMONLY)}
</a>
))}
{!hideInvite && isCommonSignup ? (
<div className={styles.title}>{t(K.REGISTER_ACCOUNT)}</div>
) : (
<div className={styles.title}>{t(K.REGISTER_AND_JOIN)}</div>
)}
<Form name="signupForm" form={form} onFinish={onFinish}>
<Form.Item
validateFirst={true}
name="username"
rules={[
{
required: true,
message: t(K.USERNAME_TIPS, {
minLength: 3,
maxLength: 32,
}),
},
{
pattern: usernamePattern,
message: t(K.USERNAME_TIPS, {
minLength: 3,
maxLength: 32,
}),
},
{
validator: (
_: any,
value: any,
callback: (value?: string) => void
) =>
validateMap["airNameValidator"](
value,
callback,
setForceUpdate
),
},
]}
>
<Input
prefix={<UserOutlined className={styles.inputPrefixIcon} />}
placeholder={t(K.USERNAME)}
/>
</Form.Item>
{enabledFeatures["enable-nickname-config"] && hideInvite && (
<Form.Item validateFirst={false} name="nickname">
<Input
prefix={
<SolutionOutlined className={styles.inputPrefixIcon} />
}
placeholder={t(K.NICKNAME)}
/>
</Form.Item>
)}
<Form.Item
name="email"
validateFirst={true}
rules={[
{ required: true, message: t(K.PLEASE_ENTER_VALID_EMAIL) },
{ type: "email", message: t(K.PLEASE_ENTER_VALID_EMAIL) },
{
validator: (
_: any,
value: any,
callback: (value?: string) => void
) =>
validateMap["airEmailValidator"](
value,
callback,
setForceUpdate
),
},
]}
>
<Input
prefix={<MailOutlined className={styles.inputPrefixIcon} />}
type="email"
placeholder={t(K.EMAIL)}
/>
</Form.Item>
<Form.Item
validateFirst={true}
name="password"
rules={[
{ required: true, message: t(K.PLEASE_INPUT_PASSWORD) },
{
pattern: passwordConfigMap[passwordLevel].regex,
message: passwordConfigMap[passwordLevel].description,
},
]}
>
<Input
prefix={<LockOutlined className={styles.inputPrefixIcon} />}
type="password"
placeholder={t(K.PASSWORD)}
/>
</Form.Item>
<Form.Item
dependencies={["password"]}
name="password2"
rules={[
{ required: true, message: t(K.PLEASE_INPUT_PASSWORD) },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(
new Error(t(K.TWO_PASSWORDS_ARE_INCONSISTENT))
);
},
}),
]}
>
<Input
prefix={<LockOutlined className={styles.inputPrefixIcon} />}
type="password"
placeholder={t(K.PASSWORD_CONFIRM)}
/>
</Form.Item>
{!hideInvite &&
(isCommonSignup ? (
<>
<Form.Item
validateFirst={true}
rules={[
{
required: true,
message: t(K.PLEASE_FILL_IN_VALID_PHONE_NUMBER),
},
{
validator: (_, value) => {
if (
/^(?=\d{11}$)^1(?:3\d|4[57]|5[^4\D]|7[^249\D]|8\d)\d{8}$/.test(
value
)
) {
setVerifyBtnDisabled(false);
return Promise.resolve();
}
setVerifyBtnDisabled(true);
return Promise.reject(
new Error(t(K.PLEASE_FILL_IN_VALID_PHONE_NUMBER))
);
},
},
]}
name="phone"
>
<Input
prefix={
<PhoneOutlined
className={styles.inputPrefixIcon}
rotate={90}
/>
}
suffix={
<Button
disabled={verifyBtnDisabled}
type="text"
onClick={handleVerifyBtnClick}
id="verifyBtn"
>
{content}
</Button>
}
placeholder={t(K.PHONE)}
/>
</Form.Item>
<Form.Item
rules={[
{
required: true,
message: t(K.PLEASE_INPUT_PHRASE),
},
{
pattern: /^\d{6}$/,
message: t(K.PLEASE_INPUT_VALID_PHRASE),
},
]}
name="verification_code"
>
<Input
prefix={
<SafetyOutlined className={styles.inputPrefixIcon} />
}
placeholder={t(K.VERIFY_CODE)}
></Input>
</Form.Item>
</>
) : (
<Form.Item
validateFirst={true}
name="invite"
rules={[
{
required: true,
message: t([K.PLEASE_FILL_IN_INVITE_CODE]),
},
{
pattern: iniviteCodePattern,
message: t([K.PLEASE_FILL_IN_INVITE_CODE]),
},
]}
>
<Input
prefix={
<GeneralIcon
icon={{
lib: "easyops",
icon: "release-management",
category: "menu",
color: "rgba(0,0,0,.25)",
}}
/>
}
type="text"
placeholder={t(K.INVITE_CODE)}
/>
</Form.Item>
))}
<Form.Item
name="terms"
valuePropName="checked"
rules={[
{
validator: (_, value) =>
value
? Promise.resolve()
: Promise.reject(new Error(t(K.AGREE_TERMS_TIPS))),
},
]}
>
<Checkbox>
{t(K.AGREE_TERMS)}
<a
onClick={() => {
showTerms();
}}
id="TermsLink"
>
{t(K.UWINTECH_TERMS)}
</a>
</Checkbox>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
style={{
width: "100%",
height: 34,
}}
id="submitBtn"
>
{t(K.REGISTER)}
</Button>
</Form.Item>
<Form.Item>
<div style={{ textAlign: "center" }}>
{t(K.ALREADY_HAVE_AN_ACCOUNT)}
<a
id="LogInLink"
onClick={() => {
getHistory().push(
createLocation({
pathname: props.loginUrl ?? "/auth/login",
})
);
}}
>
{t(K.LOGIN_IMMEDIATELY)}
</a>
</div>
</Form.Item>
</Form>
</Card>
<Modal
visible={isTermsVisible}
title={t(K.UWINTECH_TERMS)}
width={598}
okType="default"
cancelText={t(K.DISAGREE)}
okText={t(K.AGREE)}
closable={false}
onCancel={() => {
disagreeTerms();
}}
onOk={() => {
agreeTerms();
}}
>
<Terms />
</Modal>
</div>
</div>
</>
);
}
Example #17
Source File: schema.ts From am-editor with MIT License | 4 votes |
/**
* 增加规则
* 只有 type 和 attributes 时,将作为此类型全局属性,与其它所有同类型标签属性将合并
* @param rules 规则
*/
add(rules: SchemaRule | SchemaGlobal | Array<SchemaRule | SchemaGlobal>) {
rules = cloneDeep(rules);
if (!Array.isArray(rules)) {
rules = [rules];
}
rules.forEach((rule) => {
if (isSchemaRule(rule)) {
//删除全局属性已有的规则
if (rule.attributes) {
Object.keys(rule.attributes).forEach((key) => {
if (!this.data.globals[rule.type]) return;
if (key === 'style') {
Object.keys(rule.attributes!.style).forEach(
(styleName) => {
if (
this.data.globals[rule.type][key] &&
this.data.globals[rule.type][key][
styleName
] === rule.attributes!.style[styleName]
) {
delete rule.attributes!.style[
styleName
];
}
},
);
} else if (
this.data.globals[rule.type][key] ===
rule.attributes![key]
) {
delete rule.attributes![key];
}
});
}
if (rule.type === 'block') {
this.data.blocks.push(rule);
} else if (rule.type === 'inline') {
this.data.inlines.push(rule);
} else if (rule.type === 'mark') {
this.data.marks.push(rule);
}
} else if (!!this.data[`${rule.type}s`]) {
this.data.globals[rule.type] = merge(
Object.assign({}, this.data.globals[rule.type]),
rule.attributes,
);
}
});
this.updateTagMap();
//按照必要属性个数排序
const getCount = (rule: SchemaRule) => {
const aAttributes = rule.attributes || {};
const aStyles = aAttributes.style || {};
let aCount = 0;
let sCount = 0;
Object.keys(aAttributes).forEach((attributesName) => {
const attributesValue = aAttributes[attributesName];
if (
isSchemaValueObject(attributesValue) &&
attributesValue.required
)
aCount++;
});
Object.keys(aStyles).forEach((stylesName) => {
const stylesValue = aStyles[stylesName];
if (isSchemaValueObject(stylesValue) && stylesValue.required)
sCount++;
});
return [aCount, sCount];
};
const { blocks, marks, inlines } = this.data;
this._all = [...blocks, ...marks, ...inlines].sort((a, b) => {
const [aACount, aSCount] = getCount(a);
const [bACount, bSCount] = getCount(b);
if (aACount > bACount) return -1;
if (aACount === bACount)
return aSCount === bSCount ? 0 : aSCount > bSCount ? -1 : 1;
return 1;
});
}
Example #18
Source File: BrickEditor.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
export function BrickEditor({
defaultConf,
onConfChange,
aceSetOptions,
mode,
}: BrickEditorProps): React.ReactElement {
const aceRef = React.useRef(null);
const [valid, setValid] = React.useState(true);
const changed = React.useRef(false);
const [confString, setConfString] = React.useState("");
const defaultAceSetOptions = {
showLineNumbers: true,
maxLines: Infinity,
minLines: 5,
tabSize: 2,
printMargin: false,
highlightActiveLine: false,
highlightGutterLine: false,
};
const parse = (value: string, tryMode = mode): BrickConf => {
let brickConf: BrickConf;
if (value) {
try {
if (tryMode === "json") {
brickConf = JSON.parse(value);
} else {
brickConf = yaml.safeLoad(value, {
schema: yaml.JSON_SCHEMA,
json: true,
}) as BrickConf;
}
setValid(true);
} catch (e) {
setValid(false);
}
} else {
setValid(true);
}
return brickConf;
};
const serialize = (brickConf: BrickConf): string => {
let content = "";
if (brickConf) {
if (mode === "json") {
content = JSON.stringify(brickConf, null, 2);
} else {
content = yaml.safeDump(brickConf, {
schema: yaml.JSON_SCHEMA,
skipInvalid: true,
noRefs: true,
noCompatMode: true,
});
}
}
return content;
};
const onButtonCopy = (text: string, success: boolean): void => {
if (success) {
message.success("复制成功");
} else {
message.error("复制失败");
}
};
const setOptions = assign({}, defaultAceSetOptions, aceSetOptions);
const handleStoryChange = (value: string): void => {
changed.current = true;
setConfString(value);
parse(value);
};
const handleStoryBlur = (): void => {
if (!changed.current) {
return;
}
changed.current = false;
const conf = parse(confString);
if (conf) onConfChange(conf);
};
React.useEffect(() => {
const content = serialize(defaultConf);
setConfString(content);
}, [mode]);
return (
<div
className={cssStyle.editorCard}
style={{ boxShadow: valid ? "none" : "red 0px 0px 3px 1px" }}
>
<AceEditor
ref={aceRef}
theme="monokai"
mode={mode}
showGutter={true}
value={confString}
width="100%"
editorProps={{ $blockScrolling: Infinity }}
setOptions={setOptions}
onChange={handleStoryChange}
debounceChangePeriod={100}
onBlur={handleStoryBlur}
/>
<div className={cssStyle.copyIcon}>
<Clipboard
text={confString}
onCopy={onButtonCopy}
icon={{ theme: "outlined" }}
/>
</div>
</div>
);
}
Example #19
Source File: Chart.tsx From majsoul-api with MIT License | 4 votes |
ChartComponent = React.forwardRef<Chart | undefined, ChartProps>((props, ref) => {
const {
id,
className,
height = 150,
width = 300,
redraw = false,
type,
data,
plugins,
options = {},
getDatasetAtEvent,
getElementAtEvent,
getElementsAtEvent,
fallbackContent,
...rest
} = props;
const canvas = React.useRef<HTMLCanvasElement>(null);
const computedData = React.useMemo<ChartData>(() => {
if (typeof data === 'function') {
return canvas.current ? data(canvas.current) : {} as ChartData;
} else {
return { ...data };
}
}, [data, canvas.current]);
const [chart, setChart] = React.useState<Chart>();
React.useImperativeHandle<Chart | undefined, Chart | undefined>(ref, () => chart, [
chart,
]);
const renderChart = () => {
if (!canvas.current) {
return;
}
setChart(
new Chart(canvas.current, {
type,
data: computedData,
options,
plugins,
})
);
};
const onClick = (event: React.MouseEvent<HTMLCanvasElement>) => {
if (!chart) {
return;
}
getDatasetAtEvent &&
getDatasetAtEvent(
chart.getElementsAtEventForMode(
event.nativeEvent,
'dataset',
{ intersect: true },
false
),
event
);
getElementAtEvent &&
getElementAtEvent(
chart.getElementsAtEventForMode(
event.nativeEvent,
'nearest',
{ intersect: false },
false
)[0],
event
);
getElementsAtEvent &&
getElementsAtEvent(
chart.getElementsAtEventForMode(event.nativeEvent, 'index', { intersect: true }, false),
event
);
};
const updateChart = () => {
if (!chart) {
return;
}
if (options) {
chart.options = { ...options };
}
if (!chart.config.data) {
chart.config.data = computedData;
chart.update();
return;
}
const { datasets: newDataSets = [], ...newChartData } = computedData;
const { datasets: currentDataSets = [] } = chart.config.data;
// copy values
assign(chart.config.data, newChartData);
chart.config.data.datasets = newDataSets.map((newDataSet: any) => {
// given the new set, find it's current match
const currentDataSet = find(
currentDataSets,
d => d.label === newDataSet.label && d.type === newDataSet.type
);
// There is no original to update, so simply add new one
if (!currentDataSet || !newDataSet.data) {
return newDataSet;
}
if (!currentDataSet.data) {
currentDataSet.data = [];
} else {
currentDataSet.data.length = newDataSet.data.length;
}
// copy in values
assign(currentDataSet.data, newDataSet.data);
// apply dataset changes, but keep copied data
return {
...currentDataSet,
...newDataSet,
data: currentDataSet.data,
};
});
chart.update();
};
const destroyChart = () => {
if (chart) {
chart.destroy();
}
};
React.useEffect(() => {
renderChart();
return () => destroyChart();
}, []);
React.useEffect(() => {
if (redraw) {
destroyChart();
setTimeout(() => {
renderChart();
}, 0);
} else {
updateChart();
}
}, [props, computedData]);
return (
<canvas
{...rest}
height={height}
width={width}
ref={canvas}
id={id}
className={className}
onClick={onClick}
data-testid='canvas'
role='img'
>
{fallbackContent}
</canvas>
);
})