@webiny/app-record-locking 6.3.0 → 6.4.0-beta.0
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/components/HeadlessCmsActionsAcoCell.js +29 -51
- package/components/HeadlessCmsActionsAcoCell.js.map +1 -1
- package/components/HeadlessCmsContentEntry/ContentEntryGuard.js +26 -34
- package/components/HeadlessCmsContentEntry/ContentEntryGuard.js.map +1 -1
- package/components/HeadlessCmsContentEntry/ContentEntryLocker.js +70 -89
- package/components/HeadlessCmsContentEntry/ContentEntryLocker.js.map +1 -1
- package/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.js +38 -62
- package/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.js.map +1 -1
- package/components/HeadlessCmsContentEntry/index.js +0 -2
- package/components/LockedRecord/LockedRecord.js +45 -57
- package/components/LockedRecord/LockedRecord.js.map +1 -1
- package/components/LockedRecord/LockedRecordForceUnlock.js +49 -63
- package/components/LockedRecord/LockedRecordForceUnlock.js.map +1 -1
- package/components/LockedRecord/index.js +0 -2
- package/components/RecordLockingProvider.js +103 -117
- package/components/RecordLockingProvider.js.map +1 -1
- package/components/SecurityPermissions.js +11 -14
- package/components/SecurityPermissions.js.map +1 -1
- package/components/decorators/UseContentEntriesListHookDecorator.js +18 -18
- package/components/decorators/UseContentEntriesListHookDecorator.js.map +1 -1
- package/components/decorators/UseRecordsDecorator.js +13 -22
- package/components/decorators/UseRecordsDecorator.js.map +1 -1
- package/components/decorators/UseSaveEntryDecorator.js +35 -49
- package/components/decorators/UseSaveEntryDecorator.js.map +1 -1
- package/domain/RecordLocking.js +229 -291
- package/domain/RecordLocking.js.map +1 -1
- package/domain/RecordLockingClient.js +17 -16
- package/domain/RecordLockingClient.js.map +1 -1
- package/domain/RecordLockingGetLockRecord.js +13 -15
- package/domain/RecordLockingGetLockRecord.js.map +1 -1
- package/domain/RecordLockingGetLockedEntryLockRecord.js +12 -13
- package/domain/RecordLockingGetLockedEntryLockRecord.js.map +1 -1
- package/domain/RecordLockingIsEntryLocked.js +15 -14
- package/domain/RecordLockingIsEntryLocked.js.map +1 -1
- package/domain/RecordLockingListLockRecords.js +18 -24
- package/domain/RecordLockingListLockRecords.js.map +1 -1
- package/domain/RecordLockingLockEntry.js +8 -9
- package/domain/RecordLockingLockEntry.js.map +1 -1
- package/domain/RecordLockingUnlockEntry.js +12 -13
- package/domain/RecordLockingUnlockEntry.js.map +1 -1
- package/domain/RecordLockingUpdateEntryLock.js +12 -13
- package/domain/RecordLockingUpdateEntryLock.js.map +1 -1
- package/domain/abstractions/IRecordLocking.js +0 -3
- package/domain/abstractions/IRecordLockingClient.js +0 -3
- package/domain/abstractions/IRecordLockingGetLockRecord.js +0 -3
- package/domain/abstractions/IRecordLockingGetLockedEntryLockRecord.js +0 -3
- package/domain/abstractions/IRecordLockingIsEntryLocked.js +0 -3
- package/domain/abstractions/IRecordLockingListLockRecords.js +0 -3
- package/domain/abstractions/IRecordLockingLockEntry.js +0 -3
- package/domain/abstractions/IRecordLockingUnlockEntry.js +0 -3
- package/domain/abstractions/IRecordLockingUpdateEntryLock.js +0 -3
- package/domain/graphql/fields.js +3 -2
- package/domain/graphql/fields.js.map +1 -1
- package/domain/graphql/getLockRecord.js +3 -2
- package/domain/graphql/getLockRecord.js.map +1 -1
- package/domain/graphql/getLockedEntryLockRecord.js +3 -2
- package/domain/graphql/getLockedEntryLockRecord.js.map +1 -1
- package/domain/graphql/isEntryLocked.js +3 -2
- package/domain/graphql/isEntryLocked.js.map +1 -1
- package/domain/graphql/listLockRecords.js +4 -5
- package/domain/graphql/listLockRecords.js.map +1 -1
- package/domain/graphql/lockEntry.js +3 -4
- package/domain/graphql/lockEntry.js.map +1 -1
- package/domain/graphql/unlockEntry.js +3 -2
- package/domain/graphql/unlockEntry.js.map +1 -1
- package/domain/graphql/updateEntryLock.js +3 -2
- package/domain/graphql/updateEntryLock.js.map +1 -1
- package/domain/permissionsSchema.js +6 -5
- package/domain/permissionsSchema.js.map +1 -1
- package/domain/utils/createRecordLockingClient.js +5 -6
- package/domain/utils/createRecordLockingClient.js.map +1 -1
- package/domain/utils/createRecordLockingError.js +10 -11
- package/domain/utils/createRecordLockingError.js.map +1 -1
- package/features/permissions/abstractions.js +2 -1
- package/features/permissions/abstractions.js.map +1 -1
- package/features/permissions/feature.js +2 -1
- package/features/permissions/feature.js.map +1 -1
- package/hooks/index.js +0 -2
- package/hooks/usePermission.js +14 -15
- package/hooks/usePermission.js.map +1 -1
- package/hooks/useRecordLocking.js +5 -6
- package/hooks/useRecordLocking.js.map +1 -1
- package/index.js +15 -21
- package/index.js.map +1 -1
- package/package.json +12 -12
- package/types.js +0 -3
- package/utils/createCacheKey.js +9 -10
- package/utils/createCacheKey.js.map +1 -1
- package/components/HeadlessCmsContentEntry/index.js.map +0 -1
- package/components/LockedRecord/index.js.map +0 -1
- package/domain/abstractions/IRecordLocking.js.map +0 -1
- package/domain/abstractions/IRecordLockingClient.js.map +0 -1
- package/domain/abstractions/IRecordLockingGetLockRecord.js.map +0 -1
- package/domain/abstractions/IRecordLockingGetLockedEntryLockRecord.js.map +0 -1
- package/domain/abstractions/IRecordLockingIsEntryLocked.js.map +0 -1
- package/domain/abstractions/IRecordLockingListLockRecords.js.map +0 -1
- package/domain/abstractions/IRecordLockingLockEntry.js.map +0 -1
- package/domain/abstractions/IRecordLockingUnlockEntry.js.map +0 -1
- package/domain/abstractions/IRecordLockingUpdateEntryLock.js.map +0 -1
- package/hooks/index.js.map +0 -1
- package/types.js.map +0 -1
|
@@ -1,60 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import react from "react";
|
|
2
2
|
import { ContentEntryListConfig, useModel } from "@webiny/app-headless-cms";
|
|
3
|
-
import { ReactComponent
|
|
3
|
+
import { ReactComponent } from "@webiny/icons/lock.svg";
|
|
4
4
|
import { useRecordLocking } from "../hooks/index.js";
|
|
5
5
|
import { UseContentEntriesListHookDecorator } from "./decorators/UseContentEntriesListHookDecorator.js";
|
|
6
6
|
import { UseSaveEntryDecorator } from "./decorators/UseSaveEntryDecorator.js";
|
|
7
7
|
import { UseRecordsDecorator } from "./decorators/UseRecordsDecorator.js";
|
|
8
8
|
import { Icon, Tooltip } from "@webiny/admin-ui";
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
} =
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
row
|
|
28
|
-
} = useTableRow();
|
|
29
|
-
if (isFolderRow(row)) {
|
|
30
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, children);
|
|
31
|
-
}
|
|
32
|
-
const entry = getLockRecordEntry(row.id);
|
|
33
|
-
if (!isRecordLocked(entry) || !entry?.$locked) {
|
|
34
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, children);
|
|
35
|
-
}
|
|
36
|
-
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
37
|
-
side: "left",
|
|
38
|
-
content: `This ${model.name} is currently locked by ${entry.$locked.lockedBy.displayName}.`,
|
|
39
|
-
trigger: /*#__PURE__*/React.createElement(Icon, {
|
|
40
|
-
icon: /*#__PURE__*/React.createElement(LockedIcon, null),
|
|
41
|
-
label: "Locked entry",
|
|
42
|
-
color: "neutral-light"
|
|
43
|
-
})
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
const RecordLockingCellActions = Browser.Table.Column.createDecorator(Original => {
|
|
47
|
-
return function RecordLockingCellActions(props) {
|
|
48
|
-
if (props.name === "actions" && props.cell) {
|
|
49
|
-
return /*#__PURE__*/React.createElement(Original, Object.assign({}, props, {
|
|
50
|
-
cell: /*#__PURE__*/React.createElement(ActionsCell, null, props.cell)
|
|
51
|
-
}));
|
|
52
|
-
}
|
|
53
|
-
return /*#__PURE__*/React.createElement(Original, props);
|
|
54
|
-
};
|
|
55
|
-
});
|
|
56
|
-
export const HeadlessCmsActionsAcoCell = () => {
|
|
57
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(UseContentEntriesListHookDecorator, null), /*#__PURE__*/React.createElement(UseSaveEntryDecorator, null), /*#__PURE__*/React.createElement(RecordLockingCellActions, null), /*#__PURE__*/React.createElement(UseRecordsDecorator, null));
|
|
9
|
+
const { Browser: Browser } = ContentEntryListConfig;
|
|
10
|
+
const ActionsCell = ({ children })=>{
|
|
11
|
+
const { model } = useModel();
|
|
12
|
+
const { getLockRecordEntry, isRecordLocked } = useRecordLocking();
|
|
13
|
+
const { useTableRow, isFolderRow } = Browser.Table.Column;
|
|
14
|
+
const { row } = useTableRow();
|
|
15
|
+
if (isFolderRow(row)) return /*#__PURE__*/ react.createElement(react.Fragment, null, children);
|
|
16
|
+
const entry = getLockRecordEntry(row.id);
|
|
17
|
+
if (!isRecordLocked(entry) || !entry?.$locked) return /*#__PURE__*/ react.createElement(react.Fragment, null, children);
|
|
18
|
+
return /*#__PURE__*/ react.createElement(Tooltip, {
|
|
19
|
+
side: "left",
|
|
20
|
+
content: `This ${model.name} is currently locked by ${entry.$locked.lockedBy.displayName}.`,
|
|
21
|
+
trigger: /*#__PURE__*/ react.createElement(Icon, {
|
|
22
|
+
icon: /*#__PURE__*/ react.createElement(ReactComponent, null),
|
|
23
|
+
label: "Locked entry",
|
|
24
|
+
color: "neutral-light"
|
|
25
|
+
})
|
|
26
|
+
});
|
|
58
27
|
};
|
|
28
|
+
const HeadlessCmsActionsAcoCell_RecordLockingCellActions = Browser.Table.Column.createDecorator((Original)=>function(props) {
|
|
29
|
+
if ("actions" === props.name && props.cell) return /*#__PURE__*/ react.createElement(Original, {
|
|
30
|
+
...props,
|
|
31
|
+
cell: /*#__PURE__*/ react.createElement(ActionsCell, null, props.cell)
|
|
32
|
+
});
|
|
33
|
+
return /*#__PURE__*/ react.createElement(Original, props);
|
|
34
|
+
});
|
|
35
|
+
const HeadlessCmsActionsAcoCell = ()=>/*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(UseContentEntriesListHookDecorator, null), /*#__PURE__*/ react.createElement(UseSaveEntryDecorator, null), /*#__PURE__*/ react.createElement(HeadlessCmsActionsAcoCell_RecordLockingCellActions, null), /*#__PURE__*/ react.createElement(UseRecordsDecorator, null));
|
|
36
|
+
export { HeadlessCmsActionsAcoCell };
|
|
59
37
|
|
|
60
38
|
//# sourceMappingURL=HeadlessCmsActionsAcoCell.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"components/HeadlessCmsActionsAcoCell.js","sources":["../../src/components/HeadlessCmsActionsAcoCell.tsx"],"sourcesContent":["import React from \"react\";\nimport { ContentEntryListConfig, useModel } from \"@webiny/app-headless-cms\";\nimport { ReactComponent as LockedIcon } from \"@webiny/icons/lock.svg\";\nimport { useRecordLocking } from \"~/hooks/index.js\";\nimport { UseContentEntriesListHookDecorator } from \"./decorators/UseContentEntriesListHookDecorator.js\";\nimport { UseSaveEntryDecorator } from \"~/components/decorators/UseSaveEntryDecorator.js\";\nimport { UseRecordsDecorator } from \"./decorators/UseRecordsDecorator.js\";\nimport { Icon, Tooltip } from \"@webiny/admin-ui\";\n\nconst { Browser } = ContentEntryListConfig;\n\ninterface ActionsCellProps {\n children: React.ReactNode;\n}\n\nconst ActionsCell = ({ children }: ActionsCellProps) => {\n const { model } = useModel();\n const { getLockRecordEntry, isRecordLocked } = useRecordLocking();\n\n const { useTableRow, isFolderRow } = Browser.Table.Column;\n const { row } = useTableRow();\n\n if (isFolderRow(row)) {\n return <>{children}</>;\n }\n\n const entry = getLockRecordEntry(row.id);\n\n if (!isRecordLocked(entry) || !entry?.$locked) {\n return <>{children}</>;\n }\n return (\n <Tooltip\n side={\"left\"}\n content={`This ${model.name} is currently locked by ${entry.$locked.lockedBy.displayName}.`}\n trigger={<Icon icon={<LockedIcon />} label={\"Locked entry\"} color={\"neutral-light\"} />}\n />\n );\n};\n\nconst RecordLockingCellActions = Browser.Table.Column.createDecorator(Original => {\n return function RecordLockingCellActions(props) {\n if (props.name === \"actions\" && props.cell) {\n return <Original {...props} cell={<ActionsCell>{props.cell}</ActionsCell>} />;\n }\n\n return <Original {...props} />;\n };\n});\n\nexport const HeadlessCmsActionsAcoCell = () => {\n return (\n <>\n <UseContentEntriesListHookDecorator />\n <UseSaveEntryDecorator />\n <RecordLockingCellActions />\n <UseRecordsDecorator />\n </>\n );\n};\n"],"names":["Browser","ContentEntryListConfig","ActionsCell","children","model","useModel","getLockRecordEntry","isRecordLocked","useRecordLocking","useTableRow","isFolderRow","row","entry","Tooltip","Icon","LockedIcon","RecordLockingCellActions","Original","props","HeadlessCmsActionsAcoCell","UseContentEntriesListHookDecorator","UseSaveEntryDecorator","UseRecordsDecorator"],"mappings":";;;;;;;;AASA,MAAM,EAAEA,SAAAA,OAAO,EAAE,GAAGC;AAMpB,MAAMC,cAAc,CAAC,EAAEC,QAAQ,EAAoB;IAC/C,MAAM,EAAEC,KAAK,EAAE,GAAGC;IAClB,MAAM,EAAEC,kBAAkB,EAAEC,cAAc,EAAE,GAAGC;IAE/C,MAAM,EAAEC,WAAW,EAAEC,WAAW,EAAE,GAAGV,QAAQ,KAAK,CAAC,MAAM;IACzD,MAAM,EAAEW,GAAG,EAAE,GAAGF;IAEhB,IAAIC,YAAYC,MACZ,OAAO,WAAP,GAAO,0CAAGR;IAGd,MAAMS,QAAQN,mBAAmBK,IAAI,EAAE;IAEvC,IAAI,CAACJ,eAAeK,UAAU,CAACA,OAAO,SAClC,OAAO,WAAP,GAAO,0CAAGT;IAEd,OAAO,WAAP,GACI,oBAACU,SAAOA;QACJ,MAAM;QACN,SAAS,CAAC,KAAK,EAAET,MAAM,IAAI,CAAC,wBAAwB,EAAEQ,MAAM,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;QAC3F,uBAAS,oBAACE,MAAIA;YAAC,oBAAM,oBAACC,gBAAUA;YAAK,OAAO;YAAgB,OAAO;;;AAG/E;AAEA,MAAMC,qDAA2BhB,QAAQ,KAAK,CAAC,MAAM,CAAC,eAAe,CAACiB,CAAAA,WAC3D,SAAkCC,KAAK;QAC1C,IAAIA,AAAe,cAAfA,MAAM,IAAI,IAAkBA,MAAM,IAAI,EACtC,OAAO,WAAP,GAAO,oBAACD,UAAAA;YAAU,GAAGC,KAAK;YAAE,oBAAM,oBAAChB,aAAWA,MAAEgB,MAAM,IAAI;;QAG9D,OAAO,WAAP,GAAO,oBAACD,UAAaC;IACzB;AAGG,MAAMC,4BAA4B,IAC9B,WAAP,GACI,wDACI,oBAACC,oCAAkCA,OAAAA,WAAAA,GACnC,oBAACC,uBAAqBA,OAAAA,WAAAA,GACtB,oBAACL,oDAAwBA,OAAAA,WAAAA,GACzB,oBAACM,qBAAmBA"}
|
|
@@ -1,42 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import react, { useEffect, useState } from "react";
|
|
2
2
|
import { useRecordLocking } from "../../hooks/index.js";
|
|
3
3
|
import { LockedRecord } from "../LockedRecord/index.js";
|
|
4
4
|
import { OverlayLoader } from "@webiny/admin-ui";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
})();
|
|
27
|
-
}, [entry.id, loading]);
|
|
28
|
-
if (locked === undefined) {
|
|
29
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
30
|
-
className: "h-screen w-screen fixed top-0 left-0 z-20"
|
|
31
|
-
}, /*#__PURE__*/React.createElement(OverlayLoader, {
|
|
32
|
-
text: "Checking record status..."
|
|
5
|
+
const ContentEntryGuard = (props)=>{
|
|
6
|
+
const { loading, entry, model, children } = props;
|
|
7
|
+
const { fetchLockedEntryLockRecord } = useRecordLocking();
|
|
8
|
+
const [locked, setLocked] = useState(void 0);
|
|
9
|
+
useEffect(()=>{
|
|
10
|
+
if (!entry.id || loading || void 0 !== locked) return;
|
|
11
|
+
(async ()=>{
|
|
12
|
+
const result = await fetchLockedEntryLockRecord({
|
|
13
|
+
id: entry.id,
|
|
14
|
+
$lockingType: model.modelId
|
|
15
|
+
});
|
|
16
|
+
setLocked(result);
|
|
17
|
+
})();
|
|
18
|
+
}, [
|
|
19
|
+
entry.id,
|
|
20
|
+
loading
|
|
21
|
+
]);
|
|
22
|
+
if (void 0 === locked) return /*#__PURE__*/ react.createElement("div", {
|
|
23
|
+
className: "h-screen w-screen fixed top-0 left-0 z-20"
|
|
24
|
+
}, /*#__PURE__*/ react.createElement(OverlayLoader, {
|
|
25
|
+
text: "Checking record status..."
|
|
33
26
|
}));
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
record: locked
|
|
27
|
+
if (locked) return /*#__PURE__*/ react.createElement(LockedRecord, {
|
|
28
|
+
record: locked
|
|
37
29
|
});
|
|
38
|
-
|
|
39
|
-
return children;
|
|
30
|
+
return children;
|
|
40
31
|
};
|
|
32
|
+
export { ContentEntryGuard };
|
|
41
33
|
|
|
42
34
|
//# sourceMappingURL=ContentEntryGuard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"components/HeadlessCmsContentEntry/ContentEntryGuard.js","sources":["../../../src/components/HeadlessCmsContentEntry/ContentEntryGuard.tsx"],"sourcesContent":["import React, { useEffect, useState } from \"react\";\nimport { useRecordLocking } from \"~/hooks/index.js\";\nimport { LockedRecord } from \"../LockedRecord/index.js\";\nimport type { IRecordLockingLockRecord } from \"~/types.js\";\nimport type { CmsContentEntry, CmsModel } from \"@webiny/app-headless-cms/types.js\";\nimport { OverlayLoader } from \"@webiny/admin-ui\";\n\nexport interface IContentEntryGuardProps {\n loading: boolean;\n entry: CmsContentEntry;\n model: CmsModel;\n children: React.ReactElement;\n}\n\nexport const ContentEntryGuard = (props: IContentEntryGuardProps) => {\n const { loading, entry, model, children } = props;\n const { fetchLockedEntryLockRecord } = useRecordLocking();\n\n const [locked, setLocked] = useState<IRecordLockingLockRecord | null | undefined>(undefined);\n\n useEffect(() => {\n if (!entry.id || loading || locked !== undefined) {\n return;\n }\n (async () => {\n const result = await fetchLockedEntryLockRecord({\n id: entry.id,\n $lockingType: model.modelId\n });\n setLocked(result);\n })();\n }, [entry.id, loading]);\n\n if (locked === undefined) {\n return (\n <div className={\"h-screen w-screen fixed top-0 left-0 z-20\"}>\n <OverlayLoader text={\"Checking record status...\"} />\n </div>\n );\n } else if (locked) {\n return <LockedRecord record={locked} />;\n }\n\n return children;\n};\n"],"names":["ContentEntryGuard","props","loading","entry","model","children","fetchLockedEntryLockRecord","useRecordLocking","locked","setLocked","useState","undefined","useEffect","result","OverlayLoader","LockedRecord"],"mappings":";;;;AAcO,MAAMA,oBAAoB,CAACC;IAC9B,MAAM,EAAEC,OAAO,EAAEC,KAAK,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAGJ;IAC5C,MAAM,EAAEK,0BAA0B,EAAE,GAAGC;IAEvC,MAAM,CAACC,QAAQC,UAAU,GAAGC,SAAsDC;IAElFC,UAAU;QACN,IAAI,CAACT,MAAM,EAAE,IAAID,WAAWM,AAAWG,WAAXH,QACxB;QAEH;YACG,MAAMK,SAAS,MAAMP,2BAA2B;gBAC5C,IAAIH,MAAM,EAAE;gBACZ,cAAcC,MAAM,OAAO;YAC/B;YACAK,UAAUI;QACd;IACJ,GAAG;QAACV,MAAM,EAAE;QAAED;KAAQ;IAEtB,IAAIM,AAAWG,WAAXH,QACA,OAAO,WAAP,GACI,oBAAC;QAAI,WAAW;qBACZ,oBAACM,eAAaA;QAAC,MAAM;;IAG1B,IAAIN,QACP,OAAO,WAAP,GAAO,oBAACO,cAAYA;QAAC,QAAQP;;IAGjC,OAAOH;AACX"}
|
|
@@ -1,99 +1,80 @@
|
|
|
1
|
-
import
|
|
1
|
+
import react, { useEffect, useRef } from "react";
|
|
2
2
|
import { useRecordLocking } from "../../hooks/index.js";
|
|
3
3
|
import { useWebsockets } from "@webiny/app-websockets";
|
|
4
4
|
import { parseIdentifier } from "@webiny/utils";
|
|
5
5
|
import { useDialogs } from "@webiny/app-admin";
|
|
6
6
|
const autoUpdateTimeout = 20;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
let onMessageSub = websockets.onMessage(`recordLocking.entry.kickOut.${entryId}`, async incoming => {
|
|
31
|
-
const {
|
|
32
|
-
user
|
|
33
|
-
} = incoming.data;
|
|
34
|
-
onDisablePrompt();
|
|
35
|
-
removeEntryLock({
|
|
36
|
-
id: entryId,
|
|
37
|
-
$lockingType: model.modelId
|
|
38
|
-
});
|
|
39
|
-
showDialog({
|
|
40
|
-
title: "Entry was forcefully unlocked!",
|
|
41
|
-
content: /*#__PURE__*/React.createElement(React.Fragment, null, "The entry you were editing was forcefully unlocked by", " ", /*#__PURE__*/React.createElement("strong", null, user.displayName || "Unknown user"), ". Unfortunately, this means you lost the unsaved changes."),
|
|
42
|
-
acceptLabel: "Ok",
|
|
43
|
-
onClose: undefined,
|
|
44
|
-
cancelLabel: null
|
|
45
|
-
});
|
|
46
|
-
onEntryUnlocked();
|
|
47
|
-
});
|
|
48
|
-
return () => {
|
|
49
|
-
onMessageSub.off();
|
|
50
|
-
/**
|
|
51
|
-
* Lets null subscriptions, just in case it...
|
|
52
|
-
*/
|
|
53
|
-
// @ts-expect-error
|
|
54
|
-
onMessageSub = null;
|
|
55
|
-
};
|
|
56
|
-
}, [entry.id, onEntryUnlocked, model.modelId]);
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
if (!entry.id) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (entryLockerTimeout.current) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const updateLock = async () => {
|
|
65
|
-
const result = await updateEntryLock({
|
|
66
|
-
id: entry.id,
|
|
67
|
-
$lockingType: model.modelId
|
|
68
|
-
});
|
|
69
|
-
if (result.error) {
|
|
70
|
-
showDialog({
|
|
71
|
-
title: "There was an error while updating the entry lock.",
|
|
72
|
-
content: result.error.message,
|
|
73
|
-
acceptLabel: "Ok",
|
|
74
|
-
onClose: undefined,
|
|
75
|
-
cancelLabel: null
|
|
7
|
+
const ContentEntryLocker = ({ onEntryUnlocked, onDisablePrompt, entry, model, children })=>{
|
|
8
|
+
const { updateEntryLock, removeEntryLock } = useRecordLocking();
|
|
9
|
+
const websockets = useWebsockets();
|
|
10
|
+
const { showDialog } = useDialogs();
|
|
11
|
+
const entryLockerTimeout = useRef(null);
|
|
12
|
+
useEffect(()=>{
|
|
13
|
+
if (!entry.id) return;
|
|
14
|
+
const { id: entryId } = parseIdentifier(entry.id);
|
|
15
|
+
let onMessageSub = websockets.onMessage(`recordLocking.entry.kickOut.${entryId}`, async (incoming)=>{
|
|
16
|
+
const { user } = incoming.data;
|
|
17
|
+
onDisablePrompt();
|
|
18
|
+
removeEntryLock({
|
|
19
|
+
id: entryId,
|
|
20
|
+
$lockingType: model.modelId
|
|
21
|
+
});
|
|
22
|
+
showDialog({
|
|
23
|
+
title: "Entry was forcefully unlocked!",
|
|
24
|
+
content: /*#__PURE__*/ react.createElement(react.Fragment, null, "The entry you were editing was forcefully unlocked by", " ", /*#__PURE__*/ react.createElement("strong", null, user.displayName || "Unknown user"), ". Unfortunately, this means you lost the unsaved changes."),
|
|
25
|
+
acceptLabel: "Ok",
|
|
26
|
+
onClose: void 0,
|
|
27
|
+
cancelLabel: null
|
|
28
|
+
});
|
|
29
|
+
onEntryUnlocked();
|
|
76
30
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
31
|
+
return ()=>{
|
|
32
|
+
onMessageSub.off();
|
|
33
|
+
onMessageSub = null;
|
|
34
|
+
};
|
|
35
|
+
}, [
|
|
36
|
+
entry.id,
|
|
37
|
+
onEntryUnlocked,
|
|
38
|
+
model.modelId
|
|
39
|
+
]);
|
|
40
|
+
useEffect(()=>{
|
|
41
|
+
if (!entry.id) return;
|
|
42
|
+
if (entryLockerTimeout.current) return;
|
|
43
|
+
const updateLock = async ()=>{
|
|
44
|
+
const result = await updateEntryLock({
|
|
45
|
+
id: entry.id,
|
|
46
|
+
$lockingType: model.modelId
|
|
47
|
+
});
|
|
48
|
+
if (result.error) {
|
|
49
|
+
showDialog({
|
|
50
|
+
title: "There was an error while updating the entry lock.",
|
|
51
|
+
content: result.error.message,
|
|
52
|
+
acceptLabel: "Ok",
|
|
53
|
+
onClose: void 0,
|
|
54
|
+
cancelLabel: null
|
|
55
|
+
});
|
|
56
|
+
onEntryUnlocked();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
createTimeout();
|
|
60
|
+
};
|
|
61
|
+
const createTimeout = ()=>{
|
|
62
|
+
entryLockerTimeout.current = window.setTimeout(()=>{
|
|
63
|
+
updateLock();
|
|
64
|
+
}, 1000 * autoUpdateTimeout);
|
|
65
|
+
};
|
|
84
66
|
updateLock();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}, [entry.id, onEntryUnlocked]);
|
|
96
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, children);
|
|
67
|
+
return ()=>{
|
|
68
|
+
if (!entryLockerTimeout.current) return;
|
|
69
|
+
clearTimeout(entryLockerTimeout.current);
|
|
70
|
+
entryLockerTimeout.current = null;
|
|
71
|
+
};
|
|
72
|
+
}, [
|
|
73
|
+
entry.id,
|
|
74
|
+
onEntryUnlocked
|
|
75
|
+
]);
|
|
76
|
+
return /*#__PURE__*/ react.createElement(react.Fragment, null, children);
|
|
97
77
|
};
|
|
78
|
+
export { ContentEntryLocker };
|
|
98
79
|
|
|
99
80
|
//# sourceMappingURL=ContentEntryLocker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"components/HeadlessCmsContentEntry/ContentEntryLocker.js","sources":["../../../src/components/HeadlessCmsContentEntry/ContentEntryLocker.tsx"],"sourcesContent":["import React, { useEffect, useRef } from \"react\";\nimport { useRecordLocking } from \"~/hooks/index.js\";\nimport type { IRecordLockingIdentity, IRecordLockingLockRecord } from \"~/types.js\";\nimport type { IncomingGenericData } from \"@webiny/app-websockets\";\nimport { useWebsockets } from \"@webiny/app-websockets\";\nimport { parseIdentifier } from \"@webiny/utils\";\nimport { useDialogs } from \"@webiny/app-admin\";\nimport type { CmsContentEntry, CmsModel } from \"@webiny/app-headless-cms/types.js\";\n\nconst autoUpdateTimeout = 20;\n\nexport interface IContentEntryLockerProps {\n entry: CmsContentEntry;\n model: CmsModel;\n onEntryUnlocked: () => void;\n onDisablePrompt: () => void;\n children: React.ReactElement;\n}\n\nexport interface IKickOutWebsocketsMessage extends IncomingGenericData {\n data: {\n record: IRecordLockingLockRecord;\n user: IRecordLockingIdentity;\n };\n}\n\nexport const ContentEntryLocker = ({\n onEntryUnlocked,\n onDisablePrompt,\n entry,\n model,\n children\n}: IContentEntryLockerProps) => {\n const { updateEntryLock, removeEntryLock } = useRecordLocking();\n const websockets = useWebsockets();\n const { showDialog } = useDialogs();\n\n const entryLockerTimeout = useRef<number | null>(null);\n\n useEffect(() => {\n if (!entry.id) {\n return;\n }\n const { id: entryId } = parseIdentifier(entry.id);\n\n let onMessageSub = websockets.onMessage<IKickOutWebsocketsMessage>(\n `recordLocking.entry.kickOut.${entryId}`,\n async incoming => {\n const { user } = incoming.data;\n onDisablePrompt();\n removeEntryLock({\n id: entryId,\n $lockingType: model.modelId\n });\n showDialog({\n title: \"Entry was forcefully unlocked!\",\n content: (\n <>\n The entry you were editing was forcefully unlocked by{\" \"}\n <strong>{user.displayName || \"Unknown user\"}</strong>. Unfortunately,\n this means you lost the unsaved changes.\n </>\n ),\n acceptLabel: \"Ok\",\n onClose: undefined,\n cancelLabel: null\n });\n onEntryUnlocked();\n }\n );\n\n return () => {\n onMessageSub.off();\n /**\n * Lets null subscriptions, just in case it...\n */\n // @ts-expect-error\n onMessageSub = null;\n };\n }, [entry.id, onEntryUnlocked, model.modelId]);\n\n useEffect(() => {\n if (!entry.id) {\n return;\n }\n\n if (entryLockerTimeout.current) {\n return;\n }\n\n const updateLock = async () => {\n const result = await updateEntryLock({\n id: entry.id,\n $lockingType: model.modelId\n });\n if (result.error) {\n showDialog({\n title: \"There was an error while updating the entry lock.\",\n content: result.error.message,\n acceptLabel: \"Ok\",\n onClose: undefined,\n cancelLabel: null\n });\n onEntryUnlocked();\n return;\n }\n createTimeout();\n };\n\n const createTimeout = () => {\n entryLockerTimeout.current = window.setTimeout(() => {\n updateLock();\n }, autoUpdateTimeout * 1000);\n };\n\n updateLock();\n return () => {\n if (!entryLockerTimeout.current) {\n return;\n }\n clearTimeout(entryLockerTimeout.current);\n entryLockerTimeout.current = null;\n };\n }, [entry.id, onEntryUnlocked]);\n\n return <>{children}</>;\n};\n"],"names":["autoUpdateTimeout","ContentEntryLocker","onEntryUnlocked","onDisablePrompt","entry","model","children","updateEntryLock","removeEntryLock","useRecordLocking","websockets","useWebsockets","showDialog","useDialogs","entryLockerTimeout","useRef","useEffect","entryId","parseIdentifier","onMessageSub","incoming","user","undefined","updateLock","result","createTimeout","window","clearTimeout"],"mappings":";;;;;AASA,MAAMA,oBAAoB;AAiBnB,MAAMC,qBAAqB,CAAC,EAC/BC,eAAe,EACfC,eAAe,EACfC,KAAK,EACLC,KAAK,EACLC,QAAQ,EACe;IACvB,MAAM,EAAEC,eAAe,EAAEC,eAAe,EAAE,GAAGC;IAC7C,MAAMC,aAAaC;IACnB,MAAM,EAAEC,UAAU,EAAE,GAAGC;IAEvB,MAAMC,qBAAqBC,OAAsB;IAEjDC,UAAU;QACN,IAAI,CAACZ,MAAM,EAAE,EACT;QAEJ,MAAM,EAAE,IAAIa,OAAO,EAAE,GAAGC,gBAAgBd,MAAM,EAAE;QAEhD,IAAIe,eAAeT,WAAW,SAAS,CACnC,CAAC,4BAA4B,EAAEO,SAAS,EACxC,OAAMG;YACF,MAAM,EAAEC,IAAI,EAAE,GAAGD,SAAS,IAAI;YAC9BjB;YACAK,gBAAgB;gBACZ,IAAIS;gBACJ,cAAcZ,MAAM,OAAO;YAC/B;YACAO,WAAW;gBACP,OAAO;gBACP,SAAS,WAAT,GACI,0CAAE,yDACwD,mBACtD,oBAAC,gBAAQS,KAAK,WAAW,IAAI,iBAAwB;gBAI7D,aAAa;gBACb,SAASC;gBACT,aAAa;YACjB;YACApB;QACJ;QAGJ,OAAO;YACHiB,aAAa,GAAG;YAKhBA,eAAe;QACnB;IACJ,GAAG;QAACf,MAAM,EAAE;QAAEF;QAAiBG,MAAM,OAAO;KAAC;IAE7CW,UAAU;QACN,IAAI,CAACZ,MAAM,EAAE,EACT;QAGJ,IAAIU,mBAAmB,OAAO,EAC1B;QAGJ,MAAMS,aAAa;YACf,MAAMC,SAAS,MAAMjB,gBAAgB;gBACjC,IAAIH,MAAM,EAAE;gBACZ,cAAcC,MAAM,OAAO;YAC/B;YACA,IAAImB,OAAO,KAAK,EAAE;gBACdZ,WAAW;oBACP,OAAO;oBACP,SAASY,OAAO,KAAK,CAAC,OAAO;oBAC7B,aAAa;oBACb,SAASF;oBACT,aAAa;gBACjB;gBACApB;gBACA;YACJ;YACAuB;QACJ;QAEA,MAAMA,gBAAgB;YAClBX,mBAAmB,OAAO,GAAGY,OAAO,UAAU,CAAC;gBAC3CH;YACJ,GAAGvB,AAAoB,OAApBA;QACP;QAEAuB;QACA,OAAO;YACH,IAAI,CAACT,mBAAmB,OAAO,EAC3B;YAEJa,aAAab,mBAAmB,OAAO;YACvCA,mBAAmB,OAAO,GAAG;QACjC;IACJ,GAAG;QAACV,MAAM,EAAE;QAAEF;KAAgB;IAE9B,OAAO,WAAP,GAAO,0CAAGI;AACd"}
|
|
@@ -1,68 +1,44 @@
|
|
|
1
|
-
import
|
|
1
|
+
import react from "react";
|
|
2
2
|
import { useRouter } from "@webiny/app";
|
|
3
3
|
import { ContentEntryEditorConfig, ContentEntryListConfig } from "@webiny/app-headless-cms";
|
|
4
4
|
import { ContentEntryGuard } from "./ContentEntryGuard.js";
|
|
5
5
|
import { ContentEntryLocker } from "./ContentEntryLocker.js";
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} =
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return function RecordLockingSingletonContentEntry() {
|
|
44
|
-
const router = useRouter();
|
|
45
|
-
const {
|
|
46
|
-
entry,
|
|
47
|
-
contentModel,
|
|
48
|
-
loading
|
|
49
|
-
} = SingletonContentEntry.useSingletonContentEntry();
|
|
50
|
-
const props = {
|
|
51
|
-
entry,
|
|
52
|
-
model: contentModel
|
|
53
|
-
};
|
|
54
|
-
return /*#__PURE__*/React.createElement(ContentEntryGuard, Object.assign({}, props, {
|
|
55
|
-
loading: loading
|
|
56
|
-
}), /*#__PURE__*/React.createElement(ContentEntryLocker, Object.assign({}, props, {
|
|
57
|
-
onDisablePrompt: () => router.unblockTransition(),
|
|
58
|
-
onEntryUnlocked: () => {
|
|
59
|
-
// There's nowhere to go, since singleton entry doesn't have a list view.
|
|
60
|
-
}
|
|
61
|
-
}), /*#__PURE__*/React.createElement(Original, null)));
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
export const HeadlessCmsContentEntry = () => {
|
|
65
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ContentEntryDecorator, null), /*#__PURE__*/React.createElement(SingletonContentEntryDecorator, null));
|
|
66
|
-
};
|
|
6
|
+
const { ContentEntry: ContentEntry, SingletonContentEntry: SingletonContentEntry } = ContentEntryEditorConfig;
|
|
7
|
+
const ContentEntryDecorator = ContentEntry.createDecorator((Original)=>function() {
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
const { entry, contentModel, loading } = ContentEntry.useContentEntry();
|
|
10
|
+
const { navigateTo } = ContentEntryListConfig.ContentEntries.useContentEntriesList();
|
|
11
|
+
if (!entry?.id) return /*#__PURE__*/ react.createElement(Original, null);
|
|
12
|
+
const props = {
|
|
13
|
+
entry,
|
|
14
|
+
model: contentModel
|
|
15
|
+
};
|
|
16
|
+
return /*#__PURE__*/ react.createElement(ContentEntryGuard, {
|
|
17
|
+
...props,
|
|
18
|
+
loading: loading
|
|
19
|
+
}, /*#__PURE__*/ react.createElement(ContentEntryLocker, {
|
|
20
|
+
...props,
|
|
21
|
+
onDisablePrompt: ()=>router.unblockTransition(),
|
|
22
|
+
onEntryUnlocked: navigateTo
|
|
23
|
+
}, /*#__PURE__*/ react.createElement(Original, null)));
|
|
24
|
+
});
|
|
25
|
+
const SingletonContentEntryDecorator = SingletonContentEntry.createDecorator((Original)=>function() {
|
|
26
|
+
const router = useRouter();
|
|
27
|
+
const { entry, contentModel, loading } = SingletonContentEntry.useSingletonContentEntry();
|
|
28
|
+
const props = {
|
|
29
|
+
entry,
|
|
30
|
+
model: contentModel
|
|
31
|
+
};
|
|
32
|
+
return /*#__PURE__*/ react.createElement(ContentEntryGuard, {
|
|
33
|
+
...props,
|
|
34
|
+
loading: loading
|
|
35
|
+
}, /*#__PURE__*/ react.createElement(ContentEntryLocker, {
|
|
36
|
+
...props,
|
|
37
|
+
onDisablePrompt: ()=>router.unblockTransition(),
|
|
38
|
+
onEntryUnlocked: ()=>{}
|
|
39
|
+
}, /*#__PURE__*/ react.createElement(Original, null)));
|
|
40
|
+
});
|
|
41
|
+
const HeadlessCmsContentEntry = ()=>/*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(ContentEntryDecorator, null), /*#__PURE__*/ react.createElement(SingletonContentEntryDecorator, null));
|
|
42
|
+
export { HeadlessCmsContentEntry };
|
|
67
43
|
|
|
68
44
|
//# sourceMappingURL=HeadlessCmsContentEntry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.js","sources":["../../../src/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.tsx"],"sourcesContent":["import React from \"react\";\nimport { useRouter } from \"@webiny/app\";\nimport { ContentEntryEditorConfig, ContentEntryListConfig } from \"@webiny/app-headless-cms\";\nimport { ContentEntryGuard } from \"./ContentEntryGuard.js\";\nimport { ContentEntryLocker } from \"./ContentEntryLocker.js\";\n\nconst { ContentEntry, SingletonContentEntry } = ContentEntryEditorConfig;\n\nconst ContentEntryDecorator = ContentEntry.createDecorator(Original => {\n return function RecordLockingContentEntry() {\n const router = useRouter();\n const { entry, contentModel, loading } = ContentEntry.useContentEntry();\n const { navigateTo } = ContentEntryListConfig.ContentEntries.useContentEntriesList();\n /**\n * New entry does not have ID yet.\n */\n if (!entry?.id) {\n return <Original />;\n }\n /**\n * Continue with existing entry.\n */\n const props = { entry, model: contentModel };\n\n return (\n <ContentEntryGuard {...props} loading={loading}>\n <ContentEntryLocker\n {...props}\n onDisablePrompt={() => router.unblockTransition()}\n onEntryUnlocked={navigateTo}\n >\n <Original />\n </ContentEntryLocker>\n </ContentEntryGuard>\n );\n };\n});\n\nconst SingletonContentEntryDecorator = SingletonContentEntry.createDecorator(Original => {\n return function RecordLockingSingletonContentEntry() {\n const router = useRouter();\n const { entry, contentModel, loading } = SingletonContentEntry.useSingletonContentEntry();\n\n const props = { entry, model: contentModel };\n\n return (\n <ContentEntryGuard {...props} loading={loading}>\n <ContentEntryLocker\n {...props}\n onDisablePrompt={() => router.unblockTransition()}\n onEntryUnlocked={() => {\n // There's nowhere to go, since singleton entry doesn't have a list view.\n }}\n >\n <Original />\n </ContentEntryLocker>\n </ContentEntryGuard>\n );\n };\n});\n\nexport const HeadlessCmsContentEntry = () => {\n return (\n <>\n <ContentEntryDecorator />\n <SingletonContentEntryDecorator />\n </>\n );\n};\n"],"names":["ContentEntry","SingletonContentEntry","ContentEntryEditorConfig","ContentEntryDecorator","Original","router","useRouter","entry","contentModel","loading","navigateTo","ContentEntryListConfig","props","ContentEntryGuard","ContentEntryLocker","SingletonContentEntryDecorator","HeadlessCmsContentEntry"],"mappings":";;;;;AAMA,MAAM,EAAEA,cAAAA,YAAY,EAAEC,uBAAAA,qBAAqB,EAAE,GAAGC;AAEhD,MAAMC,wBAAwBH,aAAa,eAAe,CAACI,CAAAA,WAChD;QACH,MAAMC,SAASC;QACf,MAAM,EAAEC,KAAK,EAAEC,YAAY,EAAEC,OAAO,EAAE,GAAGT,aAAa,eAAe;QACrE,MAAM,EAAEU,UAAU,EAAE,GAAGC,uBAAuB,cAAc,CAAC,qBAAqB;QAIlF,IAAI,CAACJ,OAAO,IACR,OAAO,WAAP,GAAO,oBAACH,UAAAA;QAKZ,MAAMQ,QAAQ;YAAEL;YAAO,OAAOC;QAAa;QAE3C,OAAO,WAAP,GACI,oBAACK,mBAAiBA;YAAE,GAAGD,KAAK;YAAE,SAASH;yBACnC,oBAACK,oBAAkBA;YACd,GAAGF,KAAK;YACT,iBAAiB,IAAMP,OAAO,iBAAiB;YAC/C,iBAAiBK;yBAEjB,oBAACN,UAAAA;IAIjB;AAGJ,MAAMW,iCAAiCd,sBAAsB,eAAe,CAACG,CAAAA,WAClE;QACH,MAAMC,SAASC;QACf,MAAM,EAAEC,KAAK,EAAEC,YAAY,EAAEC,OAAO,EAAE,GAAGR,sBAAsB,wBAAwB;QAEvF,MAAMW,QAAQ;YAAEL;YAAO,OAAOC;QAAa;QAE3C,OAAO,WAAP,GACI,oBAACK,mBAAiBA;YAAE,GAAGD,KAAK;YAAE,SAASH;yBACnC,oBAACK,oBAAkBA;YACd,GAAGF,KAAK;YACT,iBAAiB,IAAMP,OAAO,iBAAiB;YAC/C,iBAAiB,KAEjB;yBAEA,oBAACD,UAAAA;IAIjB;AAGG,MAAMY,0BAA0B,IAC5B,WAAP,GACI,wDACI,oBAACb,uBAAqBA,OAAAA,WAAAA,GACtB,oBAACY,gCAA8BA"}
|