Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/next/components/misc/save-button.tsx
Views: 687
import { CSSProperties, useEffect, useMemo, useRef, useState } from "react";1import { cloneDeep, debounce, isEqual } from "lodash";2import { Alert, Button, Space } from "antd";3import useIsMounted from "lib/hooks/mounted";4import Loading from "components/share/loading";5import api from "lib/api/post";6import { Icon } from "@cocalc/frontend/components/icon";7import { SCHEMA } from "@cocalc/util/schema";8import { keys } from "@cocalc/util/misc";910interface Props {11edited: any;12original: any;13setOriginal: Function;14table?: string;15style?: CSSProperties;16onSave?: Function; // if onSave is async then awaits and if there is an error shows that; if not, updates state to what was saved.17isValid?: (object) => boolean; // if given, only allow saving if edited != original and isValid(edited) is true.18debounce_ms?: number; // default is DEBOUNCE_MS19disabled?: boolean; // if given, overrides internaal logic.20}2122const DEBOUNCE_MS = 1500;2324export default function SaveButton({25disabled,26edited,27original,28setOriginal,29table,30style,31onSave,32isValid,33debounce_ms,34}: Props) {35if (debounce_ms == null) debounce_ms = DEBOUNCE_MS;36const [saving, setSaving] = useState<boolean>(false);37const [error, setError] = useState<string>("");3839// Tricky hooks: We have to store the state in a ref as well so that40// we can use it in the save function, since that function41// is memoized and called from a debounced function.42const saveRef = useRef<any>({ edited, original, table });43saveRef.current = { edited, original, table };4445const isMounted = useIsMounted();4647const save = useMemo(() => {48return async () => {49const { edited, original, table } = saveRef.current;5051let changes: boolean = false;52const e: any = {};53for (const field in edited) {54if (!isEqual(original[field], edited[field])) {55e[field] = cloneDeep(edited[field]);56changes = true;57}58}59if (!changes) {60// no changes to save.61return false;62}6364for (const field of preserveFields(table)) {65e[field] = cloneDeep(edited[field]);66}67const query = { [table]: e };68if (isMounted.current) {69setSaving(true);70setError("");71}72let result;73try {74// Note -- we definitely do want to do the save75// itself, even if the component is already unmounted,76// so we don't loose changes.77result = await api("/user-query", { query });78} catch (err) {79if (!isMounted.current) return;80setError(err.message);81return false;82} finally {83if (isMounted.current) {84setSaving(false);85}86}87if (!isMounted.current) return;88if (result.error) {89setError(result.error);90} else {91setOriginal(cloneDeep(edited));92}93return true; // successful save94};95}, []);9697function doSave() {98(async () => {99const e = cloneDeep(saveRef.current.edited);100if (table) {101const didSave = await save();102if (!isMounted.current) return;103if (didSave) {104await onSave?.(e);105}106return;107}108try {109await onSave?.(e);110if (!isMounted.current) return;111setOriginal(e);112setError("");113} catch (err) {114setError(err.toString());115}116})();117}118119const doSaveDebounced = useMemo(120() => debounce(doSave, debounce_ms),121[onSave]122);123124useEffect(() => {125doSaveDebounced();126return doSaveDebounced;127}, [edited]);128129const same = isEqual(edited, original);130return (131<div style={style}>132<Button133type="primary"134disabled={135disabled ?? (saving || same || (isValid != null && !isValid(edited)))136}137onClick={doSave}138>139<Space>140<Icon name={"save"} />141{saving ? (142<Loading delay={250} before="Save">143Saving...144</Loading>145) : (146"Save"147)}148</Space>149</Button>150{!same && error && (151<Alert type="error" message={error} style={{ marginTop: "15px" }} />152)}153</div>154);155}156157function preserveFields(table: string): string[] {158return keys(SCHEMA[table].user_query?.set?.required_fields ?? {});159}160161162