json / src /store /useFile.ts
xinnni's picture
Upload 146 files
f909d7c verified
raw
history blame
No virus
6.88 kB
import debounce from "lodash.debounce";
import _get from "lodash.get";
import _set from "lodash.set";
import ReactGA from "react-ga4";
import { toast } from "react-hot-toast";
import { create } from "zustand";
import { defaultJson } from "src/constants/data";
import { FileFormat } from "src/enums/file.enum";
import { contentToJson, jsonToContent } from "src/lib/utils/json/jsonAdapter";
import { isIframe } from "src/lib/utils/widget";
import { documentSvc } from "src/services/document.service";
import useConfig from "./useConfig";
import useGraph from "./useGraph";
import useJson from "./useJson";
import useUser from "./useUser";
type SetContents = {
contents?: string;
hasChanges?: boolean;
skipUpdate?: boolean;
};
type Query = string | string[] | undefined;
interface JsonActions {
getContents: () => string;
getFormat: () => FileFormat;
getHasChanges: () => boolean;
setError: (error: string | null) => void;
setHasChanges: (hasChanges: boolean) => void;
setContents: (data: SetContents) => void;
fetchFile: (fileId: string) => void;
fetchUrl: (url: string) => void;
editContents: (path: string, value: string, callback?: () => void) => void;
setFormat: (format: FileFormat) => void;
clear: () => void;
setFile: (fileData: File) => void;
setJsonSchema: (jsonSchema: object | null) => void;
checkEditorSession: (url: Query, widget?: boolean) => void;
}
export type File = {
id: string;
views: number;
owner_email: string;
name: string;
content: string;
private: boolean;
format: FileFormat;
created_at: string;
updated_at: string;
};
const initialStates = {
fileData: null as File | null,
format: FileFormat.JSON,
contents: defaultJson,
error: null as any,
hasChanges: false,
jsonSchema: null as object | null,
};
export type FileStates = typeof initialStates;
const isURL = (value: string) => {
return /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi.test(
value
);
};
const debouncedUpdateJson = debounce((value: unknown) => {
useGraph.getState().setLoading(true);
useJson.getState().setJson(JSON.stringify(value, null, 2));
}, 800);
const filterArrayAndObjectFields = (obj: object) => {
const result = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (Array.isArray(obj[key]) || typeof obj[key] === "object") {
result[key] = obj[key];
}
}
}
return result;
};
const useFile = create<FileStates & JsonActions>()((set, get) => ({
...initialStates,
clear: () => {
set({ contents: "" });
useJson.getState().clear();
},
setJsonSchema: jsonSchema => {
if (useUser.getState().premium) set({ jsonSchema });
},
setFile: fileData => {
set({ fileData, format: fileData.format || FileFormat.JSON });
get().setContents({ contents: fileData.content, hasChanges: false });
},
getContents: () => get().contents,
getFormat: () => get().format,
getHasChanges: () => get().hasChanges,
setFormat: async format => {
try {
const prevFormat = get().format;
set({ format });
const contentJson = await contentToJson(get().contents, prevFormat);
const jsonContent = await jsonToContent(JSON.stringify(contentJson, null, 2), format);
get().setContents({ contents: jsonContent });
ReactGA.event({ action: "change_data_format", category: "User" });
} catch (error) {
get().clear();
console.warn("The content was unable to be converted, so it was cleared instead.");
}
},
setContents: async ({ contents, hasChanges = true, skipUpdate = false }) => {
try {
set({ ...(contents && { contents }), error: null, hasChanges });
const isFetchURL = window.location.href.includes("?");
const json = await contentToJson(get().contents, get().format);
if (!useConfig.getState().liveTransformEnabled && skipUpdate) return;
if (get().hasChanges && contents && contents.length < 80_000 && !isIframe() && !isFetchURL) {
sessionStorage.setItem("content", contents);
sessionStorage.setItem("format", get().format);
set({ hasChanges: true });
}
debouncedUpdateJson(json);
} catch (error: any) {
if (error?.mark?.snippet) return set({ error: error.mark.snippet });
if (error?.message) set({ error: error.message });
useJson.setState({ loading: false });
useGraph.setState({ loading: false });
}
},
setError: error => set({ error }),
setHasChanges: hasChanges => set({ hasChanges }),
fetchUrl: async url => {
try {
const res = await fetch(url);
const json = await res.json();
const jsonStr = JSON.stringify(json, null, 2);
get().setContents({ contents: jsonStr });
return useJson.setState({ json: jsonStr, loading: false });
} catch (error) {
get().clear();
toast.error("Failed to fetch document from URL!");
}
},
checkEditorSession: (url, widget) => {
if (url && typeof url === "string") {
if (isURL(url)) return get().fetchUrl(url);
return get().fetchFile(url);
}
let contents = defaultJson;
const sessionContent = sessionStorage.getItem("content") as string | null;
const format = sessionStorage.getItem("format") as FileFormat | null;
if (sessionContent && !widget) contents = sessionContent;
if (format) set({ format });
get().setContents({ contents, hasChanges: false });
},
fetchFile: async id => {
try {
const { data, error } = await documentSvc.getById(id);
if (error) throw error;
if (data?.length) get().setFile(data[0]);
if (data?.length === 0) throw new Error("Document not found");
} catch (error: any) {
if (error?.message) toast.error(error?.message);
get().setContents({ contents: defaultJson, hasChanges: false });
}
},
editContents: async (path, value, callback) => {
try {
if (!value) return;
let tempValue = value;
const pathJson = _get(JSON.parse(useJson.getState().json), path.replace("{Root}.", ""));
const changedValue = JSON.parse(value);
if (typeof changedValue !== "string") {
tempValue = {
...filterArrayAndObjectFields(pathJson),
...changedValue,
};
} else {
tempValue = tempValue.replaceAll('"', "");
}
const newJson = _set(
JSON.parse(useJson.getState().json),
path.replace("{Root}.", ""),
tempValue
);
const contents = await jsonToContent(JSON.stringify(newJson, null, 2), get().format);
get().setContents({ contents });
if (callback) callback();
} catch (error) {
toast.error("Invalid Property!");
}
},
}));
export default useFile;