@firecms/entity_history 3.0.0-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ import { EntityCallbacks } from "@firecms/core";
2
+ import equal from "react-fast-compare"
3
+
4
+ export const entityHistoryCallbacks: EntityCallbacks = {
5
+ onSaveSuccess: async (props) => {
6
+
7
+ const changedFields = props.previousValues ? findChangedFields(props.previousValues, props.values) : null;
8
+ const uid = props.context.authController.user?.uid;
9
+ props.context.dataSource.saveEntity({
10
+ path: props.path + "/" + props.entityId + "/__history",
11
+ values: {
12
+ ...props.values,
13
+ __metadata: {
14
+ changed_fields: changedFields,
15
+ updated_on: new Date(),
16
+ updated_by: uid,
17
+ }
18
+ },
19
+ status: "new"
20
+ }).then(() => {
21
+ console.debug("History saved for", props.path, props.entityId);
22
+ });
23
+ }
24
+ }
25
+
26
+ function findChangedFields<M extends object>(oldValues: M, newValues: M, prefix: string = ""): string[] {
27
+ const changedFields: string[] = [];
28
+
29
+ // Handle null/undefined cases
30
+ if (equal(oldValues, newValues)) return changedFields;
31
+ if (!oldValues || !newValues) return [prefix || "."];
32
+
33
+ // Get all unique keys from both objects
34
+ const allKeys = new Set([
35
+ ...Object.keys(oldValues),
36
+ ...Object.keys(newValues)
37
+ ]);
38
+
39
+ for (const key of allKeys) {
40
+ const oldValue = oldValues[key as keyof M];
41
+ const newValue = newValues[key as keyof M];
42
+ const currentPath = prefix ? `${prefix}.${key}` : key;
43
+
44
+ // If key exists only in one object
45
+ if ((key in oldValues) !== (key in newValues)) {
46
+ changedFields.push(currentPath);
47
+ continue;
48
+ }
49
+
50
+ // If values are identical (deep equality)
51
+ if (equal(oldValue, newValue)) continue;
52
+
53
+ // Handle arrays
54
+ if (Array.isArray(oldValue) && Array.isArray(newValue)) {
55
+ if (oldValue.length !== newValue.length) {
56
+ changedFields.push(currentPath);
57
+ } else {
58
+ // Check if any array element changed
59
+ for (let i = 0; i < oldValue.length; i++) {
60
+ if (
61
+ typeof oldValue[i] === "object" && oldValue[i] !== null &&
62
+ typeof newValue[i] === "object" && newValue[i] !== null
63
+ ) {
64
+ const nestedChanges = findChangedFields(
65
+ oldValue[i] as object,
66
+ newValue[i] as object,
67
+ `${currentPath}[${i}]`
68
+ );
69
+ if (nestedChanges.length > 0) {
70
+ changedFields.push(currentPath);
71
+ break;
72
+ }
73
+ } else if (!equal(oldValue[i], newValue[i])) {
74
+ changedFields.push(currentPath);
75
+ break;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ // Handle nested objects
81
+ else if (
82
+ typeof oldValue === "object" && oldValue !== null &&
83
+ typeof newValue === "object" && newValue !== null
84
+ ) {
85
+ const nestedChanges = findChangedFields(
86
+ oldValue as object,
87
+ newValue as object,
88
+ currentPath
89
+ );
90
+ changedFields.push(...nestedChanges);
91
+ }
92
+ // Handle primitives
93
+ else {
94
+ changedFields.push(currentPath);
95
+ }
96
+ }
97
+
98
+ return changedFields;
99
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./useEntityHistoryPlugin";
2
+ export * from "./HistoryControllerProvider";
@@ -0,0 +1,61 @@
1
+ import { useCallback, useMemo } from "react";
2
+ import { EntityCollection, FireCMSPlugin, mergeCallbacks, User } from "@firecms/core";
3
+ import { EntityHistoryView } from "./components/EntityHistoryView";
4
+ import { HistoryIcon } from "@firecms/ui";
5
+ import { entityHistoryCallbacks } from "./entity_history_callbacks";
6
+ import { HistoryControllerProvider } from "./HistoryControllerProvider";
7
+
8
+ /**
9
+ * This plugin adds a history view to the entity side panel.
10
+ */
11
+ export function useEntityHistoryPlugin(props?: EntityHistoryPluginProps): FireCMSPlugin<any, any, any, EntityHistoryPluginProps> {
12
+
13
+ const { defaultEnabled = false } = props ?? {};
14
+
15
+ const modifyCollection = useCallback((collection: EntityCollection) => {
16
+ if (collection.history === true || (defaultEnabled && collection.history !== false)) {
17
+ return {
18
+ ...collection,
19
+ entityViews: [
20
+ ...(collection.entityViews ?? []),
21
+ {
22
+ key: "__history",
23
+ name: "History",
24
+ tabComponent: <HistoryIcon size={"small"}/>,
25
+ Builder: EntityHistoryView,
26
+ position: "start"
27
+ }
28
+ ],
29
+ callbacks: mergeCallbacks(collection.callbacks, entityHistoryCallbacks)
30
+ } satisfies EntityCollection;
31
+ }
32
+ return collection;
33
+ }, []);
34
+
35
+ return useMemo(() => ({
36
+ key: "entity_history",
37
+ provider: {
38
+ Component: HistoryControllerProvider,
39
+ props: {
40
+ getUser: props?.getUser
41
+ }
42
+ },
43
+ collection: {
44
+ modifyCollection
45
+ }
46
+ } satisfies FireCMSPlugin), [props]);
47
+ }
48
+
49
+ export type EntityHistoryPluginProps = {
50
+ /**
51
+ * If true, the history view will be enabled to all collections by default.
52
+ * Each collection can override this value by setting the `history` property.
53
+ */
54
+ defaultEnabled?: boolean;
55
+
56
+ /**
57
+ * Function to get the user object from the uid.
58
+ * @param uid
59
+ */
60
+ getUser?: (uid: string) => User | null;
61
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />