@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.
- package/LICENSE +114 -0
- package/README.md +124 -0
- package/dist/HistoryControllerProvider.d.ts +15 -0
- package/dist/components/EntityHistoryEntry.d.ts +17 -0
- package/dist/components/EntityHistoryView.d.ts +2 -0
- package/dist/components/UserChip.d.ts +4 -0
- package/dist/entity_history_callbacks.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.es.js +578 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +592 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/useEntityHistoryPlugin.d.ts +17 -0
- package/package.json +86 -0
- package/src/HistoryControllerProvider.tsx +40 -0
- package/src/components/EntityHistoryEntry.tsx +164 -0
- package/src/components/EntityHistoryView.tsx +234 -0
- package/src/components/UserChip.tsx +15 -0
- package/src/entity_history_callbacks.ts +99 -0
- package/src/index.ts +2 -0
- package/src/useEntityHistoryPlugin.tsx +61 -0
- package/src/vite-env.d.ts +1 -0
@@ -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,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" />
|