@webiny/app-record-locking 6.4.5-beta.0 → 6.6.0-alpha.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/RecordLockingModule.d.ts +2 -0
- package/RecordLockingModule.js +74 -0
- package/RecordLockingModule.js.map +1 -0
- package/{components/SecurityPermissions.js → SecurityPermissions.js} +1 -1
- package/SecurityPermissions.js.map +1 -0
- package/features/acquireLock/AcquireLockGateway.d.ts +12 -0
- package/features/acquireLock/AcquireLockGateway.js +43 -0
- package/features/acquireLock/AcquireLockGateway.js.map +1 -0
- package/features/acquireLock/AcquireLockUseCase.d.ts +10 -0
- package/features/acquireLock/AcquireLockUseCase.js +18 -0
- package/features/acquireLock/AcquireLockUseCase.js.map +1 -0
- package/features/acquireLock/abstractions.d.ts +19 -0
- package/features/acquireLock/abstractions.js +6 -0
- package/features/acquireLock/abstractions.js.map +1 -0
- package/features/acquireLock/feature.d.ts +3 -0
- package/features/acquireLock/feature.js +19 -0
- package/features/acquireLock/feature.js.map +1 -0
- package/features/checkLockStatus/CheckLockStatusGateway.d.ts +12 -0
- package/features/checkLockStatus/CheckLockStatusGateway.js +43 -0
- package/features/checkLockStatus/CheckLockStatusGateway.js.map +1 -0
- package/features/checkLockStatus/CheckLockStatusUseCase.d.ts +10 -0
- package/features/checkLockStatus/CheckLockStatusUseCase.js +18 -0
- package/features/checkLockStatus/CheckLockStatusUseCase.js.map +1 -0
- package/features/checkLockStatus/abstractions.d.ts +19 -0
- package/features/checkLockStatus/abstractions.js +6 -0
- package/features/checkLockStatus/abstractions.js.map +1 -0
- package/features/checkLockStatus/feature.d.ts +3 -0
- package/features/checkLockStatus/feature.js +19 -0
- package/features/checkLockStatus/feature.js.map +1 -0
- package/features/feature.d.ts +1 -0
- package/features/feature.js +19 -0
- package/features/feature.js.map +1 -0
- package/features/forceUnlock/ForceUnlockGateway.d.ts +12 -0
- package/features/forceUnlock/ForceUnlockGateway.js +44 -0
- package/features/forceUnlock/ForceUnlockGateway.js.map +1 -0
- package/features/forceUnlock/ForceUnlockUseCase.d.ts +10 -0
- package/features/forceUnlock/ForceUnlockUseCase.js +18 -0
- package/features/forceUnlock/ForceUnlockUseCase.js.map +1 -0
- package/features/forceUnlock/abstractions.d.ts +19 -0
- package/features/forceUnlock/abstractions.js +6 -0
- package/features/forceUnlock/abstractions.js.map +1 -0
- package/features/forceUnlock/feature.d.ts +3 -0
- package/features/forceUnlock/feature.js +19 -0
- package/features/forceUnlock/feature.js.map +1 -0
- package/{domain/graphql/fields.js → features/graphqlFields.js} +1 -1
- package/features/graphqlFields.js.map +1 -0
- package/features/listLockRecords/ListLockRecordsGateway.d.ts +12 -0
- package/features/listLockRecords/ListLockRecordsGateway.js +48 -0
- package/features/listLockRecords/ListLockRecordsGateway.js.map +1 -0
- package/features/listLockRecords/ListLockRecordsUseCase.d.ts +10 -0
- package/features/listLockRecords/ListLockRecordsUseCase.js +18 -0
- package/features/listLockRecords/ListLockRecordsUseCase.js.map +1 -0
- package/features/listLockRecords/abstractions.d.ts +22 -0
- package/features/listLockRecords/abstractions.js +6 -0
- package/features/listLockRecords/abstractions.js.map +1 -0
- package/features/listLockRecords/feature.d.ts +3 -0
- package/features/listLockRecords/feature.js +19 -0
- package/features/listLockRecords/feature.js.map +1 -0
- package/features/permissions/abstractions.d.ts +1 -1
- package/features/permissions/abstractions.js +1 -1
- package/features/permissions/abstractions.js.map +1 -1
- package/features/permissions/feature.js +1 -1
- package/features/permissions/feature.js.map +1 -1
- package/features/releaseLock/ReleaseLockGateway.d.ts +12 -0
- package/features/releaseLock/ReleaseLockGateway.js +44 -0
- package/features/releaseLock/ReleaseLockGateway.js.map +1 -0
- package/features/releaseLock/ReleaseLockUseCase.d.ts +10 -0
- package/features/releaseLock/ReleaseLockUseCase.js +18 -0
- package/features/releaseLock/ReleaseLockUseCase.js.map +1 -0
- package/features/releaseLock/abstractions.d.ts +19 -0
- package/features/releaseLock/abstractions.js +6 -0
- package/features/releaseLock/abstractions.js.map +1 -0
- package/features/releaseLock/feature.d.ts +3 -0
- package/features/releaseLock/feature.js +19 -0
- package/features/releaseLock/feature.js.map +1 -0
- package/index.d.ts +0 -5
- package/index.js +12 -13
- package/index.js.map +1 -1
- package/package.json +13 -17
- package/permissions/permissionsSchema.js.map +1 -0
- package/presentation/entryLocking/ContentEntryFormPresenterDecorator.d.ts +22 -0
- package/presentation/entryLocking/ContentEntryFormPresenterDecorator.js +61 -0
- package/presentation/entryLocking/ContentEntryFormPresenterDecorator.js.map +1 -0
- package/presentation/entryLocking/RecordLockingPresenter.d.ts +32 -0
- package/presentation/entryLocking/RecordLockingPresenter.js +164 -0
- package/presentation/entryLocking/RecordLockingPresenter.js.map +1 -0
- package/presentation/entryLocking/SingleEntryContentDecorator.d.ts +2 -0
- package/presentation/entryLocking/SingleEntryContentDecorator.js +72 -0
- package/presentation/entryLocking/SingleEntryContentDecorator.js.map +1 -0
- package/presentation/entryLocking/SingleEntryPresenterDecorator.d.ts +15 -0
- package/presentation/entryLocking/SingleEntryPresenterDecorator.js +40 -0
- package/presentation/entryLocking/SingleEntryPresenterDecorator.js.map +1 -0
- package/presentation/entryLocking/abstractions.d.ts +26 -0
- package/presentation/entryLocking/abstractions.js +5 -0
- package/presentation/entryLocking/abstractions.js.map +1 -0
- package/presentation/entryLocking/components/ForceUnlockButton.d.ts +11 -0
- package/presentation/entryLocking/components/ForceUnlockButton.js +37 -0
- package/presentation/entryLocking/components/ForceUnlockButton.js.map +1 -0
- package/presentation/entryLocking/components/LockedEntryOverlay.d.ts +10 -0
- package/presentation/entryLocking/components/LockedEntryOverlay.js +52 -0
- package/presentation/entryLocking/components/LockedEntryOverlay.js.map +1 -0
- package/presentation/entryLocking/components/RecordLockingGuard.d.ts +12 -0
- package/presentation/entryLocking/components/RecordLockingGuard.js +74 -0
- package/presentation/entryLocking/components/RecordLockingGuard.js.map +1 -0
- package/presentation/entryLocking/feature.d.ts +3 -0
- package/presentation/entryLocking/feature.js +21 -0
- package/presentation/entryLocking/feature.js.map +1 -0
- package/presentation/listLocking/ContentEntriesPresenterDecorator.d.ts +24 -0
- package/presentation/listLocking/ContentEntriesPresenterDecorator.js +67 -0
- package/presentation/listLocking/ContentEntriesPresenterDecorator.js.map +1 -0
- package/presentation/listLocking/ListLockRecordsPresenter.d.ts +17 -0
- package/presentation/listLocking/ListLockRecordsPresenter.js +52 -0
- package/presentation/listLocking/ListLockRecordsPresenter.js.map +1 -0
- package/presentation/listLocking/abstractions.d.ts +16 -0
- package/presentation/listLocking/abstractions.js +5 -0
- package/presentation/listLocking/abstractions.js.map +1 -0
- package/presentation/listLocking/components/ListLockRecordsSync.d.ts +3 -0
- package/presentation/listLocking/components/ListLockRecordsSync.js +26 -0
- package/presentation/listLocking/components/ListLockRecordsSync.js.map +1 -0
- package/presentation/listLocking/components/LockIndicatorCell.d.ts +2 -0
- package/presentation/listLocking/components/LockIndicatorCell.js +39 -0
- package/presentation/listLocking/components/LockIndicatorCell.js.map +1 -0
- package/presentation/listLocking/feature.d.ts +3 -0
- package/presentation/listLocking/feature.js +19 -0
- package/presentation/listLocking/feature.js.map +1 -0
- package/types.d.ts +1 -62
- package/components/HeadlessCmsActionsAcoCell.d.ts +0 -2
- package/components/HeadlessCmsActionsAcoCell.js +0 -38
- package/components/HeadlessCmsActionsAcoCell.js.map +0 -1
- package/components/HeadlessCmsContentEntry/ContentEntryGuard.d.ts +0 -9
- package/components/HeadlessCmsContentEntry/ContentEntryGuard.js +0 -34
- package/components/HeadlessCmsContentEntry/ContentEntryGuard.js.map +0 -1
- package/components/HeadlessCmsContentEntry/ContentEntryLocker.d.ts +0 -18
- package/components/HeadlessCmsContentEntry/ContentEntryLocker.js +0 -80
- package/components/HeadlessCmsContentEntry/ContentEntryLocker.js.map +0 -1
- package/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.d.ts +0 -2
- package/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.js +0 -44
- package/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.js.map +0 -1
- package/components/HeadlessCmsContentEntry/index.d.ts +0 -1
- package/components/HeadlessCmsContentEntry/index.js +0 -1
- package/components/LockedRecord/LockedRecord.d.ts +0 -6
- package/components/LockedRecord/LockedRecord.js +0 -54
- package/components/LockedRecord/LockedRecord.js.map +0 -1
- package/components/LockedRecord/LockedRecordForceUnlock.d.ts +0 -9
- package/components/LockedRecord/LockedRecordForceUnlock.js +0 -57
- package/components/LockedRecord/LockedRecordForceUnlock.js.map +0 -1
- package/components/LockedRecord/index.d.ts +0 -1
- package/components/LockedRecord/index.js +0 -1
- package/components/RecordLockingProvider.d.ts +0 -7
- package/components/RecordLockingProvider.js +0 -111
- package/components/RecordLockingProvider.js.map +0 -1
- package/components/SecurityPermissions.js.map +0 -1
- package/components/decorators/UseContentEntriesListHookDecorator.d.ts +0 -1
- package/components/decorators/UseContentEntriesListHookDecorator.js +0 -23
- package/components/decorators/UseContentEntriesListHookDecorator.js.map +0 -1
- package/components/decorators/UseRecordsDecorator.d.ts +0 -8
- package/components/decorators/UseRecordsDecorator.js +0 -16
- package/components/decorators/UseRecordsDecorator.js.map +0 -1
- package/components/decorators/UseSaveEntryDecorator.d.ts +0 -1
- package/components/decorators/UseSaveEntryDecorator.js +0 -41
- package/components/decorators/UseSaveEntryDecorator.js.map +0 -1
- package/domain/RecordLocking.d.ts +0 -28
- package/domain/RecordLocking.js +0 -259
- package/domain/RecordLocking.js.map +0 -1
- package/domain/RecordLockingClient.d.ts +0 -12
- package/domain/RecordLockingClient.js +0 -20
- package/domain/RecordLockingClient.js.map +0 -1
- package/domain/RecordLockingGetLockRecord.d.ts +0 -11
- package/domain/RecordLockingGetLockRecord.js +0 -19
- package/domain/RecordLockingGetLockRecord.js.map +0 -1
- package/domain/RecordLockingGetLockedEntryLockRecord.d.ts +0 -11
- package/domain/RecordLockingGetLockedEntryLockRecord.js +0 -18
- package/domain/RecordLockingGetLockedEntryLockRecord.js.map +0 -1
- package/domain/RecordLockingIsEntryLocked.d.ts +0 -11
- package/domain/RecordLockingIsEntryLocked.js +0 -20
- package/domain/RecordLockingIsEntryLocked.js.map +0 -1
- package/domain/RecordLockingListLockRecords.d.ts +0 -12
- package/domain/RecordLockingListLockRecords.js +0 -25
- package/domain/RecordLockingListLockRecords.js.map +0 -1
- package/domain/RecordLockingLockEntry.d.ts +0 -11
- package/domain/RecordLockingLockEntry.js +0 -12
- package/domain/RecordLockingLockEntry.js.map +0 -1
- package/domain/RecordLockingUnlockEntry.d.ts +0 -11
- package/domain/RecordLockingUnlockEntry.js +0 -18
- package/domain/RecordLockingUnlockEntry.js.map +0 -1
- package/domain/RecordLockingUpdateEntryLock.d.ts +0 -11
- package/domain/RecordLockingUpdateEntryLock.js +0 -18
- package/domain/RecordLockingUpdateEntryLock.js.map +0 -1
- package/domain/abstractions/IRecordLocking.d.ts +0 -25
- package/domain/abstractions/IRecordLocking.js +0 -0
- package/domain/abstractions/IRecordLockingClient.d.ts +0 -6
- package/domain/abstractions/IRecordLockingClient.js +0 -0
- package/domain/abstractions/IRecordLockingGetLockRecord.d.ts +0 -12
- package/domain/abstractions/IRecordLockingGetLockRecord.js +0 -0
- package/domain/abstractions/IRecordLockingGetLockedEntryLockRecord.d.ts +0 -12
- package/domain/abstractions/IRecordLockingGetLockedEntryLockRecord.js +0 -0
- package/domain/abstractions/IRecordLockingIsEntryLocked.d.ts +0 -8
- package/domain/abstractions/IRecordLockingIsEntryLocked.js +0 -0
- package/domain/abstractions/IRecordLockingListLockRecords.d.ts +0 -19
- package/domain/abstractions/IRecordLockingListLockRecords.js +0 -0
- package/domain/abstractions/IRecordLockingLockEntry.d.ts +0 -12
- package/domain/abstractions/IRecordLockingLockEntry.js +0 -0
- package/domain/abstractions/IRecordLockingUnlockEntry.d.ts +0 -13
- package/domain/abstractions/IRecordLockingUnlockEntry.js +0 -0
- package/domain/abstractions/IRecordLockingUpdateEntryLock.d.ts +0 -12
- package/domain/abstractions/IRecordLockingUpdateEntryLock.js +0 -0
- package/domain/graphql/fields.js.map +0 -1
- package/domain/graphql/getLockRecord.d.ts +0 -12
- package/domain/graphql/getLockRecord.js +0 -19
- package/domain/graphql/getLockRecord.js.map +0 -1
- package/domain/graphql/getLockedEntryLockRecord.d.ts +0 -12
- package/domain/graphql/getLockedEntryLockRecord.js +0 -19
- package/domain/graphql/getLockedEntryLockRecord.js.map +0 -1
- package/domain/graphql/isEntryLocked.d.ts +0 -12
- package/domain/graphql/isEntryLocked.js +0 -17
- package/domain/graphql/isEntryLocked.js.map +0 -1
- package/domain/graphql/listLockRecords.d.ts +0 -17
- package/domain/graphql/listLockRecords.js +0 -25
- package/domain/graphql/listLockRecords.js.map +0 -1
- package/domain/graphql/lockEntry.d.ts +0 -12
- package/domain/graphql/lockEntry.js +0 -19
- package/domain/graphql/lockEntry.js.map +0 -1
- package/domain/graphql/unlockEntry.d.ts +0 -12
- package/domain/graphql/unlockEntry.js +0 -19
- package/domain/graphql/unlockEntry.js.map +0 -1
- package/domain/graphql/updateEntryLock.d.ts +0 -12
- package/domain/graphql/updateEntryLock.js +0 -19
- package/domain/graphql/updateEntryLock.js.map +0 -1
- package/domain/permissionsSchema.js.map +0 -1
- package/domain/utils/createRecordLockingClient.d.ts +0 -3
- package/domain/utils/createRecordLockingClient.js +0 -11
- package/domain/utils/createRecordLockingClient.js.map +0 -1
- package/domain/utils/createRecordLockingError.d.ts +0 -6
- package/domain/utils/createRecordLockingError.js +0 -15
- package/domain/utils/createRecordLockingError.js.map +0 -1
- package/hooks/index.d.ts +0 -2
- package/hooks/index.js +0 -2
- package/hooks/usePermission.d.ts +0 -3
- package/hooks/usePermission.js +0 -19
- package/hooks/usePermission.js.map +0 -1
- package/hooks/useRecordLocking.d.ts +0 -2
- package/hooks/useRecordLocking.js +0 -11
- package/hooks/useRecordLocking.js.map +0 -1
- package/utils/createCacheKey.d.ts +0 -3
- package/utils/createCacheKey.js +0 -14
- package/utils/createCacheKey.js.map +0 -1
- /package/{components/SecurityPermissions.d.ts → SecurityPermissions.d.ts} +0 -0
- /package/{domain/graphql/fields.d.ts → features/graphqlFields.d.ts} +0 -0
- /package/{domain → permissions}/permissionsSchema.d.ts +0 -0
- /package/{domain → permissions}/permissionsSchema.js +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { computed, makeAutoObservable } from "mobx";
|
|
2
|
+
import { ContentEntryFormPresenter } from "@webiny/app-headless-cms/presentation/contentEntries/form/abstractions.js";
|
|
3
|
+
import { RecordLockingPresenter } from "./abstractions.js";
|
|
4
|
+
class ContentEntryFormPresenterWithLocking {
|
|
5
|
+
constructor(lockingPresenter, original){
|
|
6
|
+
this.lockingPresenter = lockingPresenter;
|
|
7
|
+
this.original = original;
|
|
8
|
+
makeAutoObservable(this, {
|
|
9
|
+
lockingPresenter: false,
|
|
10
|
+
original: false,
|
|
11
|
+
vm: computed
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
get vm() {
|
|
15
|
+
const base = this.original.vm;
|
|
16
|
+
if (base.isNewEntry) return base;
|
|
17
|
+
const canEdit = this.lockingPresenter.vm.canEdit;
|
|
18
|
+
return {
|
|
19
|
+
...base,
|
|
20
|
+
canSave: base.canSave && canEdit,
|
|
21
|
+
canPublish: base.canPublish && canEdit,
|
|
22
|
+
canUnpublish: base.canUnpublish && canEdit,
|
|
23
|
+
canDelete: base.canDelete && canEdit
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async saveRevision(options) {
|
|
27
|
+
const result = await this.original.saveRevision(options);
|
|
28
|
+
if (result) await this.lockingPresenter.refreshLock();
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
publishRevision() {
|
|
32
|
+
return this.original.publishRevision();
|
|
33
|
+
}
|
|
34
|
+
unpublishRevision() {
|
|
35
|
+
return this.original.unpublishRevision();
|
|
36
|
+
}
|
|
37
|
+
deleteEntry() {
|
|
38
|
+
return this.original.deleteEntry();
|
|
39
|
+
}
|
|
40
|
+
loadRevision(id) {
|
|
41
|
+
return this.original.loadRevision(id);
|
|
42
|
+
}
|
|
43
|
+
setFolderId(folderId) {
|
|
44
|
+
this.original.setFolderId(folderId);
|
|
45
|
+
}
|
|
46
|
+
newEntry(initialValues) {
|
|
47
|
+
return this.original.newEntry(initialValues);
|
|
48
|
+
}
|
|
49
|
+
reset() {
|
|
50
|
+
return this.original.reset();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const ContentEntryFormPresenterLockingDecorator = ContentEntryFormPresenter.createDecorator({
|
|
54
|
+
decorator: ContentEntryFormPresenterWithLocking,
|
|
55
|
+
dependencies: [
|
|
56
|
+
RecordLockingPresenter
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
export { ContentEntryFormPresenterLockingDecorator };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=ContentEntryFormPresenterDecorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/entryLocking/ContentEntryFormPresenterDecorator.js","sources":["../../../src/presentation/entryLocking/ContentEntryFormPresenterDecorator.ts"],"sourcesContent":["import { computed, makeAutoObservable } from \"mobx\";\nimport {\n ContentEntryFormPresenter,\n type IContentEntryFormPresenter,\n type IContentEntryFormViewModel\n} from \"@webiny/app-headless-cms/presentation/contentEntries/form/abstractions.js\";\nimport { RecordLockingPresenter } from \"./abstractions.js\";\n\nclass ContentEntryFormPresenterWithLocking implements IContentEntryFormPresenter {\n constructor(\n private lockingPresenter: RecordLockingPresenter.Interface,\n private original: IContentEntryFormPresenter\n ) {\n makeAutoObservable<ContentEntryFormPresenterWithLocking, \"lockingPresenter\" | \"original\">(\n this,\n {\n lockingPresenter: false,\n original: false,\n vm: computed\n }\n );\n }\n\n get vm(): IContentEntryFormViewModel {\n const base = this.original.vm;\n\n if (base.isNewEntry) {\n return base;\n }\n\n const canEdit = this.lockingPresenter.vm.canEdit;\n\n return {\n ...base,\n canSave: base.canSave && canEdit,\n canPublish: base.canPublish && canEdit,\n canUnpublish: base.canUnpublish && canEdit,\n canDelete: base.canDelete && canEdit\n };\n }\n\n async saveRevision(options?: { skipValidation?: boolean }): Promise<boolean> {\n const result = await this.original.saveRevision(options);\n\n if (result) {\n await this.lockingPresenter.refreshLock();\n }\n\n return result;\n }\n\n publishRevision(): Promise<boolean> {\n return this.original.publishRevision();\n }\n\n unpublishRevision(): Promise<boolean> {\n return this.original.unpublishRevision();\n }\n\n deleteEntry(): Promise<boolean> {\n return this.original.deleteEntry();\n }\n\n loadRevision(id: string): Promise<void> {\n return this.original.loadRevision(id);\n }\n\n setFolderId(folderId: string | null): void {\n this.original.setFolderId(folderId);\n }\n\n newEntry(initialValues?: Record<string, unknown>): void {\n return this.original.newEntry(initialValues);\n }\n\n reset(): void {\n return this.original.reset();\n }\n}\n\nexport const ContentEntryFormPresenterLockingDecorator = ContentEntryFormPresenter.createDecorator({\n decorator: ContentEntryFormPresenterWithLocking,\n dependencies: [RecordLockingPresenter]\n});\n"],"names":["ContentEntryFormPresenterWithLocking","lockingPresenter","original","makeAutoObservable","computed","base","canEdit","options","result","id","folderId","initialValues","ContentEntryFormPresenterLockingDecorator","ContentEntryFormPresenter","RecordLockingPresenter"],"mappings":";;;AAQA,MAAMA;IACF,YACYC,gBAAkD,EAClDC,QAAoC,CAC9C;aAFUD,gBAAgB,GAAhBA;aACAC,QAAQ,GAARA;QAERC,mBACI,IAAI,EACJ;YACI,kBAAkB;YAClB,UAAU;YACV,IAAIC;QACR;IAER;IAEA,IAAI,KAAiC;QACjC,MAAMC,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE;QAE7B,IAAIA,KAAK,UAAU,EACf,OAAOA;QAGX,MAAMC,UAAU,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,OAAO;QAEhD,OAAO;YACH,GAAGD,IAAI;YACP,SAASA,KAAK,OAAO,IAAIC;YACzB,YAAYD,KAAK,UAAU,IAAIC;YAC/B,cAAcD,KAAK,YAAY,IAAIC;YACnC,WAAWD,KAAK,SAAS,IAAIC;QACjC;IACJ;IAEA,MAAM,aAAaC,OAAsC,EAAoB;QACzE,MAAMC,SAAS,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,CAACD;QAEhD,IAAIC,QACA,MAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW;QAG3C,OAAOA;IACX;IAEA,kBAAoC;QAChC,OAAO,IAAI,CAAC,QAAQ,CAAC,eAAe;IACxC;IAEA,oBAAsC;QAClC,OAAO,IAAI,CAAC,QAAQ,CAAC,iBAAiB;IAC1C;IAEA,cAAgC;QAC5B,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW;IACpC;IAEA,aAAaC,EAAU,EAAiB;QACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,CAACA;IACtC;IAEA,YAAYC,QAAuB,EAAQ;QACvC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAACA;IAC9B;IAEA,SAASC,aAAuC,EAAQ;QACpD,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAACA;IAClC;IAEA,QAAc;QACV,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK;IAC9B;AACJ;AAEO,MAAMC,4CAA4CC,0BAA0B,eAAe,CAAC;IAC/F,WAAWb;IACX,cAAc;QAACc;KAAuB;AAC1C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { IdentityContext } from "@webiny/app-admin/features/security/IdentityContext/abstractions.js";
|
|
2
|
+
import { AcquireLockUseCase } from "../../features/acquireLock/abstractions.js";
|
|
3
|
+
import { CheckLockStatusUseCase } from "../../features/checkLockStatus/abstractions.js";
|
|
4
|
+
import { ReleaseLockUseCase } from "../../features/releaseLock/abstractions.js";
|
|
5
|
+
import { ForceUnlockUseCase } from "../../features/forceUnlock/abstractions.js";
|
|
6
|
+
import { type IRecordLockingPresenter, type IRecordLockingViewModel, type IKickOutData } from "./abstractions.js";
|
|
7
|
+
declare class RecordLockingPresenterImpl implements IRecordLockingPresenter {
|
|
8
|
+
private acquireLockUseCase;
|
|
9
|
+
private checkLockStatusUseCase;
|
|
10
|
+
private releaseLockUseCase;
|
|
11
|
+
private forceUnlockUseCase;
|
|
12
|
+
private identityContext;
|
|
13
|
+
private _status;
|
|
14
|
+
private _lockRecord;
|
|
15
|
+
private _entryId;
|
|
16
|
+
private _type;
|
|
17
|
+
private _heartbeatTimer;
|
|
18
|
+
constructor(acquireLockUseCase: AcquireLockUseCase.Interface, checkLockStatusUseCase: CheckLockStatusUseCase.Interface, releaseLockUseCase: ReleaseLockUseCase.Interface, forceUnlockUseCase: ForceUnlockUseCase.Interface, identityContext: IdentityContext.Interface);
|
|
19
|
+
get vm(): IRecordLockingViewModel;
|
|
20
|
+
private checkCanForceUnlock;
|
|
21
|
+
init(entryId: string, type: string): Promise<void>;
|
|
22
|
+
refreshLock(): Promise<void>;
|
|
23
|
+
forceUnlock(): Promise<boolean>;
|
|
24
|
+
handleKickOut(data: IKickOutData): void;
|
|
25
|
+
dispose(): void;
|
|
26
|
+
private startHeartbeat;
|
|
27
|
+
private stopHeartbeat;
|
|
28
|
+
}
|
|
29
|
+
export declare const RecordLockingPresenter: typeof RecordLockingPresenterImpl & {
|
|
30
|
+
__abstraction: import("@webiny/di").Abstraction<IRecordLockingPresenter>;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { computed, makeAutoObservable, runInAction } from "mobx";
|
|
2
|
+
import { parseIdentifier } from "@webiny/utils";
|
|
3
|
+
import { IdentityContext } from "@webiny/app-admin/features/security/IdentityContext/abstractions.js";
|
|
4
|
+
import { AcquireLockUseCase } from "../../features/acquireLock/abstractions.js";
|
|
5
|
+
import { CheckLockStatusUseCase } from "../../features/checkLockStatus/abstractions.js";
|
|
6
|
+
import { ReleaseLockUseCase } from "../../features/releaseLock/abstractions.js";
|
|
7
|
+
import { ForceUnlockUseCase } from "../../features/forceUnlock/abstractions.js";
|
|
8
|
+
import { RecordLockingPresenter } from "./abstractions.js";
|
|
9
|
+
const HEARTBEAT_INTERVAL_MS = 20000;
|
|
10
|
+
class RecordLockingPresenterImpl {
|
|
11
|
+
constructor(acquireLockUseCase, checkLockStatusUseCase, releaseLockUseCase, forceUnlockUseCase, identityContext){
|
|
12
|
+
this.acquireLockUseCase = acquireLockUseCase;
|
|
13
|
+
this.checkLockStatusUseCase = checkLockStatusUseCase;
|
|
14
|
+
this.releaseLockUseCase = releaseLockUseCase;
|
|
15
|
+
this.forceUnlockUseCase = forceUnlockUseCase;
|
|
16
|
+
this.identityContext = identityContext;
|
|
17
|
+
this._status = null;
|
|
18
|
+
this._lockRecord = null;
|
|
19
|
+
this._entryId = null;
|
|
20
|
+
this._type = null;
|
|
21
|
+
this._heartbeatTimer = null;
|
|
22
|
+
makeAutoObservable(this, {
|
|
23
|
+
acquireLockUseCase: false,
|
|
24
|
+
checkLockStatusUseCase: false,
|
|
25
|
+
releaseLockUseCase: false,
|
|
26
|
+
forceUnlockUseCase: false,
|
|
27
|
+
identityContext: false,
|
|
28
|
+
vm: computed
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
get vm() {
|
|
32
|
+
return {
|
|
33
|
+
status: this._status,
|
|
34
|
+
lockRecord: this._lockRecord,
|
|
35
|
+
lockedByUserName: this._lockRecord?.lockedBy?.displayName ?? null,
|
|
36
|
+
canForceUnlock: this.checkCanForceUnlock(),
|
|
37
|
+
canEdit: "acquired" === this._status
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
checkCanForceUnlock() {
|
|
41
|
+
const identity = this.identityContext.getIdentity();
|
|
42
|
+
if (identity.getPermission("recordLocking.*")) return true;
|
|
43
|
+
const permission = identity.getPermission("recordLocking");
|
|
44
|
+
return permission?.canForceUnlock === true;
|
|
45
|
+
}
|
|
46
|
+
async init(entryId, type) {
|
|
47
|
+
const { id } = parseIdentifier(entryId);
|
|
48
|
+
this._entryId = id;
|
|
49
|
+
this._type = type;
|
|
50
|
+
this._status = "checking";
|
|
51
|
+
try {
|
|
52
|
+
const lockRecord = await this.checkLockStatusUseCase.execute({
|
|
53
|
+
id,
|
|
54
|
+
type
|
|
55
|
+
});
|
|
56
|
+
if (lockRecord) return void runInAction(()=>{
|
|
57
|
+
this._status = "locked";
|
|
58
|
+
this._lockRecord = lockRecord;
|
|
59
|
+
});
|
|
60
|
+
const acquired = await this.acquireLockUseCase.execute({
|
|
61
|
+
id,
|
|
62
|
+
type
|
|
63
|
+
});
|
|
64
|
+
runInAction(()=>{
|
|
65
|
+
this._status = "acquired";
|
|
66
|
+
this._lockRecord = acquired;
|
|
67
|
+
});
|
|
68
|
+
this.startHeartbeat(id, type);
|
|
69
|
+
} catch {
|
|
70
|
+
runInAction(()=>{
|
|
71
|
+
this._status = "error";
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async refreshLock() {
|
|
76
|
+
if ("acquired" !== this._status || !this._entryId || !this._type) return;
|
|
77
|
+
try {
|
|
78
|
+
const record = await this.acquireLockUseCase.execute({
|
|
79
|
+
id: this._entryId,
|
|
80
|
+
type: this._type
|
|
81
|
+
});
|
|
82
|
+
runInAction(()=>{
|
|
83
|
+
this._lockRecord = record;
|
|
84
|
+
});
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
async forceUnlock() {
|
|
88
|
+
if (!this._lockRecord || !this._type) return false;
|
|
89
|
+
try {
|
|
90
|
+
await this.forceUnlockUseCase.execute({
|
|
91
|
+
id: this._lockRecord.targetId,
|
|
92
|
+
type: this._type
|
|
93
|
+
});
|
|
94
|
+
const acquired = await this.acquireLockUseCase.execute({
|
|
95
|
+
id: this._lockRecord.targetId,
|
|
96
|
+
type: this._type
|
|
97
|
+
});
|
|
98
|
+
runInAction(()=>{
|
|
99
|
+
this._status = "acquired";
|
|
100
|
+
this._lockRecord = acquired;
|
|
101
|
+
});
|
|
102
|
+
this.startHeartbeat(this._lockRecord.targetId, this._type);
|
|
103
|
+
return true;
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
handleKickOut(data) {
|
|
109
|
+
this.stopHeartbeat();
|
|
110
|
+
runInAction(()=>{
|
|
111
|
+
this._status = "kicked-out";
|
|
112
|
+
this._lockRecord = data.record;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
dispose() {
|
|
116
|
+
this.stopHeartbeat();
|
|
117
|
+
if ("acquired" === this._status && this._entryId && this._type) this.releaseLockUseCase.execute({
|
|
118
|
+
id: this._entryId,
|
|
119
|
+
type: this._type
|
|
120
|
+
}).catch(()=>{});
|
|
121
|
+
this._status = null;
|
|
122
|
+
this._lockRecord = null;
|
|
123
|
+
this._entryId = null;
|
|
124
|
+
this._type = null;
|
|
125
|
+
}
|
|
126
|
+
startHeartbeat(entryId, type) {
|
|
127
|
+
this.stopHeartbeat();
|
|
128
|
+
this._heartbeatTimer = setInterval(async ()=>{
|
|
129
|
+
try {
|
|
130
|
+
const record = await this.acquireLockUseCase.execute({
|
|
131
|
+
id: entryId,
|
|
132
|
+
type
|
|
133
|
+
});
|
|
134
|
+
runInAction(()=>{
|
|
135
|
+
this._lockRecord = record;
|
|
136
|
+
});
|
|
137
|
+
} catch {
|
|
138
|
+
this.stopHeartbeat();
|
|
139
|
+
runInAction(()=>{
|
|
140
|
+
this._status = "error";
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
144
|
+
}
|
|
145
|
+
stopHeartbeat() {
|
|
146
|
+
if (null !== this._heartbeatTimer) {
|
|
147
|
+
clearInterval(this._heartbeatTimer);
|
|
148
|
+
this._heartbeatTimer = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const RecordLockingPresenter_RecordLockingPresenter = RecordLockingPresenter.createImplementation({
|
|
153
|
+
implementation: RecordLockingPresenterImpl,
|
|
154
|
+
dependencies: [
|
|
155
|
+
AcquireLockUseCase,
|
|
156
|
+
CheckLockStatusUseCase,
|
|
157
|
+
ReleaseLockUseCase,
|
|
158
|
+
ForceUnlockUseCase,
|
|
159
|
+
IdentityContext
|
|
160
|
+
]
|
|
161
|
+
});
|
|
162
|
+
export { RecordLockingPresenter_RecordLockingPresenter as RecordLockingPresenter };
|
|
163
|
+
|
|
164
|
+
//# sourceMappingURL=RecordLockingPresenter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/entryLocking/RecordLockingPresenter.js","sources":["../../../src/presentation/entryLocking/RecordLockingPresenter.ts"],"sourcesContent":["import { computed, makeAutoObservable, runInAction } from \"mobx\";\nimport { parseIdentifier } from \"@webiny/utils\";\nimport { IdentityContext } from \"@webiny/app-admin/features/security/IdentityContext/abstractions.js\";\nimport { AcquireLockUseCase } from \"~/features/acquireLock/abstractions.js\";\nimport { CheckLockStatusUseCase } from \"~/features/checkLockStatus/abstractions.js\";\nimport { ReleaseLockUseCase } from \"~/features/releaseLock/abstractions.js\";\nimport { ForceUnlockUseCase } from \"~/features/forceUnlock/abstractions.js\";\nimport {\n RecordLockingPresenter as Abstraction,\n type IRecordLockingPresenter,\n type IRecordLockingViewModel,\n type IKickOutData,\n type RecordLockingStatus\n} from \"./abstractions.js\";\nimport type { IRecordLockingLockRecord } from \"~/types.js\";\n\nconst HEARTBEAT_INTERVAL_MS = 20_000;\n\nclass RecordLockingPresenterImpl implements IRecordLockingPresenter {\n private _status: RecordLockingStatus = null;\n private _lockRecord: IRecordLockingLockRecord | null = null;\n private _entryId: string | null = null;\n private _type: string | null = null;\n private _heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n private acquireLockUseCase: AcquireLockUseCase.Interface,\n private checkLockStatusUseCase: CheckLockStatusUseCase.Interface,\n private releaseLockUseCase: ReleaseLockUseCase.Interface,\n private forceUnlockUseCase: ForceUnlockUseCase.Interface,\n private identityContext: IdentityContext.Interface\n ) {\n makeAutoObservable<\n RecordLockingPresenterImpl,\n | \"acquireLockUseCase\"\n | \"checkLockStatusUseCase\"\n | \"releaseLockUseCase\"\n | \"forceUnlockUseCase\"\n | \"identityContext\"\n >(this, {\n acquireLockUseCase: false,\n checkLockStatusUseCase: false,\n releaseLockUseCase: false,\n forceUnlockUseCase: false,\n identityContext: false,\n vm: computed\n });\n }\n\n get vm(): IRecordLockingViewModel {\n return {\n status: this._status,\n lockRecord: this._lockRecord,\n lockedByUserName: this._lockRecord?.lockedBy?.displayName ?? null,\n canForceUnlock: this.checkCanForceUnlock(),\n canEdit: this._status === \"acquired\"\n };\n }\n\n private checkCanForceUnlock(): boolean {\n const identity = this.identityContext.getIdentity();\n\n if (identity.getPermission(\"recordLocking.*\")) {\n return true;\n }\n\n const permission = identity.getPermission(\"recordLocking\");\n return permission?.canForceUnlock === true;\n }\n\n async init(entryId: string, type: string): Promise<void> {\n const { id } = parseIdentifier(entryId);\n\n this._entryId = id;\n this._type = type;\n this._status = \"checking\";\n\n try {\n const lockRecord = await this.checkLockStatusUseCase.execute({\n id,\n type\n });\n\n if (lockRecord) {\n runInAction(() => {\n this._status = \"locked\";\n this._lockRecord = lockRecord;\n });\n return;\n }\n\n const acquired = await this.acquireLockUseCase.execute({\n id,\n type\n });\n\n runInAction(() => {\n this._status = \"acquired\";\n this._lockRecord = acquired;\n });\n\n this.startHeartbeat(id, type);\n } catch {\n runInAction(() => {\n this._status = \"error\";\n });\n }\n }\n\n async refreshLock(): Promise<void> {\n if (this._status !== \"acquired\" || !this._entryId || !this._type) {\n return;\n }\n\n try {\n const record = await this.acquireLockUseCase.execute({\n id: this._entryId,\n type: this._type\n });\n\n runInAction(() => {\n this._lockRecord = record;\n });\n } catch {\n // Heartbeat will retry\n }\n }\n\n async forceUnlock(): Promise<boolean> {\n if (!this._lockRecord || !this._type) {\n return false;\n }\n\n try {\n await this.forceUnlockUseCase.execute({\n id: this._lockRecord.targetId,\n type: this._type\n });\n\n const acquired = await this.acquireLockUseCase.execute({\n id: this._lockRecord.targetId,\n type: this._type\n });\n\n runInAction(() => {\n this._status = \"acquired\";\n this._lockRecord = acquired;\n });\n\n this.startHeartbeat(this._lockRecord.targetId, this._type);\n\n return true;\n } catch {\n return false;\n }\n }\n\n handleKickOut(data: IKickOutData): void {\n this.stopHeartbeat();\n\n runInAction(() => {\n this._status = \"kicked-out\";\n this._lockRecord = data.record;\n });\n }\n\n dispose(): void {\n this.stopHeartbeat();\n\n if (this._status === \"acquired\" && this._entryId && this._type) {\n this.releaseLockUseCase\n .execute({ id: this._entryId, type: this._type })\n .catch(() => {});\n }\n\n this._status = null;\n this._lockRecord = null;\n this._entryId = null;\n this._type = null;\n }\n\n private startHeartbeat(entryId: string, type: string): void {\n this.stopHeartbeat();\n\n this._heartbeatTimer = setInterval(async () => {\n try {\n const record = await this.acquireLockUseCase.execute({ id: entryId, type });\n runInAction(() => {\n this._lockRecord = record;\n });\n } catch {\n this.stopHeartbeat();\n runInAction(() => {\n this._status = \"error\";\n });\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private stopHeartbeat(): void {\n if (this._heartbeatTimer !== null) {\n clearInterval(this._heartbeatTimer);\n this._heartbeatTimer = null;\n }\n }\n}\n\nexport const RecordLockingPresenter = Abstraction.createImplementation({\n implementation: RecordLockingPresenterImpl,\n dependencies: [\n AcquireLockUseCase,\n CheckLockStatusUseCase,\n ReleaseLockUseCase,\n ForceUnlockUseCase,\n IdentityContext\n ]\n});\n"],"names":["HEARTBEAT_INTERVAL_MS","RecordLockingPresenterImpl","acquireLockUseCase","checkLockStatusUseCase","releaseLockUseCase","forceUnlockUseCase","identityContext","makeAutoObservable","computed","identity","permission","entryId","type","id","parseIdentifier","lockRecord","runInAction","acquired","record","data","setInterval","clearInterval","RecordLockingPresenter","Abstraction","AcquireLockUseCase","CheckLockStatusUseCase","ReleaseLockUseCase","ForceUnlockUseCase","IdentityContext"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import react from "react";
|
|
2
|
+
import { observer } from "mobx-react-lite";
|
|
3
|
+
import { useContainer, useRouter } from "@webiny/app";
|
|
4
|
+
import { useDialogs } from "@webiny/app-admin";
|
|
5
|
+
import { useWebsockets } from "@webiny/app-websockets";
|
|
6
|
+
import { OverlayLoader } from "@webiny/admin-ui";
|
|
7
|
+
import { parseIdentifier } from "@webiny/utils";
|
|
8
|
+
import { SingleEntryFormContent } from "@webiny/app-headless-cms/presentation/contentEntries/views/SingleEntryFormContent.js";
|
|
9
|
+
import { SingleEntryPresenter } from "@webiny/app-headless-cms/presentation/contentEntries/singleEntry/abstractions.js";
|
|
10
|
+
import { CmsModelContext } from "@webiny/app-headless-cms/features/contentEntry/abstractions.js";
|
|
11
|
+
import { RecordLockingPresenter } from "./abstractions.js";
|
|
12
|
+
import { LockedEntryOverlay } from "./components/LockedEntryOverlay.js";
|
|
13
|
+
const SingleEntryContentDecorator = SingleEntryFormContent.createDecorator((Original)=>observer(function(props) {
|
|
14
|
+
const container = useContainer();
|
|
15
|
+
const websockets = useWebsockets();
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const { showDialog } = useDialogs();
|
|
18
|
+
const singleEntryPresenter = react.useMemo(()=>container.resolve(SingleEntryPresenter), [
|
|
19
|
+
container
|
|
20
|
+
]);
|
|
21
|
+
const lockingPresenter = react.useMemo(()=>container.resolve(RecordLockingPresenter), [
|
|
22
|
+
container
|
|
23
|
+
]);
|
|
24
|
+
const modelAccessor = react.useMemo(()=>container.resolve(CmsModelContext), [
|
|
25
|
+
container
|
|
26
|
+
]);
|
|
27
|
+
const entry = singleEntryPresenter.vm.entry;
|
|
28
|
+
react.useEffect(()=>{
|
|
29
|
+
if (!entry?.id) return;
|
|
30
|
+
lockingPresenter.init(entry.id, modelAccessor.getModel().modelId);
|
|
31
|
+
return ()=>lockingPresenter.dispose();
|
|
32
|
+
}, [
|
|
33
|
+
entry?.id
|
|
34
|
+
]);
|
|
35
|
+
react.useEffect(()=>{
|
|
36
|
+
if (!entry?.id) return;
|
|
37
|
+
const { id } = parseIdentifier(entry.id);
|
|
38
|
+
const sub = websockets.onMessage(`recordLocking.entry.kickOut.${id}`, (incoming)=>lockingPresenter.handleKickOut(incoming.data));
|
|
39
|
+
return ()=>{
|
|
40
|
+
sub.off();
|
|
41
|
+
};
|
|
42
|
+
}, [
|
|
43
|
+
entry?.id
|
|
44
|
+
]);
|
|
45
|
+
react.useEffect(()=>{
|
|
46
|
+
if ("kicked-out" !== lockingPresenter.vm.status) return;
|
|
47
|
+
router.unblockTransition();
|
|
48
|
+
showDialog({
|
|
49
|
+
title: "Entry was forcefully unlocked!",
|
|
50
|
+
content: /*#__PURE__*/ react.createElement(react.Fragment, null, "The entry you were editing was forcefully unlocked by", " ", /*#__PURE__*/ react.createElement("strong", null, lockingPresenter.vm.lockRecord?.lockedBy?.displayName || "Unknown user"), ". Unfortunately, this means you lost the unsaved changes."),
|
|
51
|
+
acceptLabel: "Ok",
|
|
52
|
+
onClose: void 0,
|
|
53
|
+
cancelLabel: null
|
|
54
|
+
});
|
|
55
|
+
}, [
|
|
56
|
+
lockingPresenter.vm.status
|
|
57
|
+
]);
|
|
58
|
+
if (!entry?.id) return /*#__PURE__*/ react.createElement(Original, props);
|
|
59
|
+
if ("checking" === lockingPresenter.vm.status) return /*#__PURE__*/ react.createElement(OverlayLoader, {
|
|
60
|
+
text: "Checking record status..."
|
|
61
|
+
});
|
|
62
|
+
if ("locked" === lockingPresenter.vm.status) return /*#__PURE__*/ react.createElement(LockedEntryOverlay, {
|
|
63
|
+
vm: lockingPresenter.vm,
|
|
64
|
+
entryTitle: entry.meta?.title,
|
|
65
|
+
onForceUnlock: ()=>lockingPresenter.forceUnlock(),
|
|
66
|
+
onNavigateBack: ()=>{}
|
|
67
|
+
});
|
|
68
|
+
return /*#__PURE__*/ react.createElement(Original, props);
|
|
69
|
+
}));
|
|
70
|
+
export { SingleEntryContentDecorator };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=SingleEntryContentDecorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/entryLocking/SingleEntryContentDecorator.js","sources":["../../../src/presentation/entryLocking/SingleEntryContentDecorator.tsx"],"sourcesContent":["import React from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { useContainer, useRouter } from \"@webiny/app\";\nimport { useDialogs } from \"@webiny/app-admin\";\nimport { useWebsockets } from \"@webiny/app-websockets\";\nimport { OverlayLoader } from \"@webiny/admin-ui\";\nimport { parseIdentifier } from \"@webiny/utils\";\nimport { SingleEntryFormContent } from \"@webiny/app-headless-cms/presentation/contentEntries/views/SingleEntryFormContent.js\";\nimport { SingleEntryPresenter } from \"@webiny/app-headless-cms/presentation/contentEntries/singleEntry/abstractions.js\";\nimport { CmsModelContext } from \"@webiny/app-headless-cms/features/contentEntry/abstractions.js\";\nimport { RecordLockingPresenter } from \"./abstractions.js\";\nimport { LockedEntryOverlay } from \"./components/LockedEntryOverlay.js\";\nimport type { IKickOutData } from \"./abstractions.js\";\nimport type { IncomingGenericData } from \"@webiny/app-websockets\";\n\ninterface KickOutMessage extends IncomingGenericData {\n data: IKickOutData;\n}\n\nexport const SingleEntryContentDecorator = SingleEntryFormContent.createDecorator(Original => {\n return observer(function RecordLockingSingleEntryContent(props) {\n const container = useContainer();\n const websockets = useWebsockets();\n const router = useRouter();\n const { showDialog } = useDialogs();\n\n const singleEntryPresenter = React.useMemo(\n () => container.resolve(SingleEntryPresenter),\n [container]\n );\n\n const lockingPresenter = React.useMemo(\n () => container.resolve(RecordLockingPresenter),\n [container]\n );\n\n const modelAccessor = React.useMemo(() => container.resolve(CmsModelContext), [container]);\n\n const entry = singleEntryPresenter.vm.entry;\n\n React.useEffect(() => {\n if (!entry?.id) {\n return;\n }\n lockingPresenter.init(entry.id, modelAccessor.getModel().modelId);\n return () => lockingPresenter.dispose();\n }, [entry?.id]);\n\n React.useEffect(() => {\n if (!entry?.id) {\n return;\n }\n const { id } = parseIdentifier(entry.id);\n const sub = websockets.onMessage<KickOutMessage>(\n `recordLocking.entry.kickOut.${id}`,\n incoming => lockingPresenter.handleKickOut(incoming.data)\n );\n return () => {\n sub.off();\n };\n }, [entry?.id]);\n\n React.useEffect(() => {\n if (lockingPresenter.vm.status !== \"kicked-out\") {\n return;\n }\n\n router.unblockTransition();\n showDialog({\n title: \"Entry was forcefully unlocked!\",\n content: (\n <>\n The entry you were editing was forcefully unlocked by{\" \"}\n <strong>\n {lockingPresenter.vm.lockRecord?.lockedBy?.displayName ||\n \"Unknown user\"}\n </strong>\n . Unfortunately, this means you lost the unsaved changes.\n </>\n ),\n acceptLabel: \"Ok\",\n onClose: undefined,\n cancelLabel: null\n });\n }, [lockingPresenter.vm.status]);\n\n if (!entry?.id) {\n return <Original {...props} />;\n }\n\n if (lockingPresenter.vm.status === \"checking\") {\n return <OverlayLoader text={\"Checking record status...\"} />;\n }\n\n if (lockingPresenter.vm.status === \"locked\") {\n return (\n <LockedEntryOverlay\n vm={lockingPresenter.vm}\n entryTitle={entry.meta?.title}\n onForceUnlock={() => lockingPresenter.forceUnlock()}\n onNavigateBack={() => {\n // It's a single-entry model, so there's nowhere to go.\n }}\n />\n );\n }\n\n return <Original {...props} />;\n });\n});\n"],"names":["SingleEntryContentDecorator","SingleEntryFormContent","Original","observer","props","container","useContainer","websockets","useWebsockets","router","useRouter","showDialog","useDialogs","singleEntryPresenter","React","SingleEntryPresenter","lockingPresenter","RecordLockingPresenter","modelAccessor","CmsModelContext","entry","id","parseIdentifier","sub","incoming","undefined","OverlayLoader","LockedEntryOverlay"],"mappings":";;;;;;;;;;;;AAmBO,MAAMA,8BAA8BC,uBAAuB,eAAe,CAACC,CAAAA,WACvEC,SAAS,SAAyCC,KAAK;QAC1D,MAAMC,YAAYC;QAClB,MAAMC,aAAaC;QACnB,MAAMC,SAASC;QACf,MAAM,EAAEC,UAAU,EAAE,GAAGC;QAEvB,MAAMC,uBAAuBC,MAAAA,OAAa,CACtC,IAAMT,UAAU,OAAO,CAACU,uBACxB;YAACV;SAAU;QAGf,MAAMW,mBAAmBF,MAAAA,OAAa,CAClC,IAAMT,UAAU,OAAO,CAACY,yBACxB;YAACZ;SAAU;QAGf,MAAMa,gBAAgBJ,MAAAA,OAAa,CAAC,IAAMT,UAAU,OAAO,CAACc,kBAAkB;YAACd;SAAU;QAEzF,MAAMe,QAAQP,qBAAqB,EAAE,CAAC,KAAK;QAE3CC,MAAAA,SAAe,CAAC;YACZ,IAAI,CAACM,OAAO,IACR;YAEJJ,iBAAiB,IAAI,CAACI,MAAM,EAAE,EAAEF,cAAc,QAAQ,GAAG,OAAO;YAChE,OAAO,IAAMF,iBAAiB,OAAO;QACzC,GAAG;YAACI,OAAO;SAAG;QAEdN,MAAAA,SAAe,CAAC;YACZ,IAAI,CAACM,OAAO,IACR;YAEJ,MAAM,EAAEC,EAAE,EAAE,GAAGC,gBAAgBF,MAAM,EAAE;YACvC,MAAMG,MAAMhB,WAAW,SAAS,CAC5B,CAAC,4BAA4B,EAAEc,IAAI,EACnCG,CAAAA,WAAYR,iBAAiB,aAAa,CAACQ,SAAS,IAAI;YAE5D,OAAO;gBACHD,IAAI,GAAG;YACX;QACJ,GAAG;YAACH,OAAO;SAAG;QAEdN,MAAAA,SAAe,CAAC;YACZ,IAAIE,AAA+B,iBAA/BA,iBAAiB,EAAE,CAAC,MAAM,EAC1B;YAGJP,OAAO,iBAAiB;YACxBE,WAAW;gBACP,OAAO;gBACP,SAAS,WAAT,GACI,0CAAE,yDACwD,mBACtD,oBAAC,gBACIK,iBAAiB,EAAE,CAAC,UAAU,EAAE,UAAU,eACvC,iBACC;gBAIjB,aAAa;gBACb,SAASS;gBACT,aAAa;YACjB;QACJ,GAAG;YAACT,iBAAiB,EAAE,CAAC,MAAM;SAAC;QAE/B,IAAI,CAACI,OAAO,IACR,OAAO,WAAP,GAAO,oBAAClB,UAAaE;QAGzB,IAAIY,AAA+B,eAA/BA,iBAAiB,EAAE,CAAC,MAAM,EAC1B,OAAO,WAAP,GAAO,oBAACU,eAAaA;YAAC,MAAM;;QAGhC,IAAIV,AAA+B,aAA/BA,iBAAiB,EAAE,CAAC,MAAM,EAC1B,OAAO,WAAP,GACI,oBAACW,oBAAkBA;YACf,IAAIX,iBAAiB,EAAE;YACvB,YAAYI,MAAM,IAAI,EAAE;YACxB,eAAe,IAAMJ,iBAAiB,WAAW;YACjD,gBAAgB,KAEhB;;QAKZ,OAAO,WAAP,GAAO,oBAACd,UAAaE;IACzB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ISingleEntryPresenter, type ISingleEntryViewModel } from "@webiny/app-headless-cms/presentation/contentEntries/singleEntry/abstractions.js";
|
|
2
|
+
import { RecordLockingPresenter } from "./abstractions.js";
|
|
3
|
+
declare class SingleEntryPresenterWithLocking implements ISingleEntryPresenter {
|
|
4
|
+
private lockingPresenter;
|
|
5
|
+
private original;
|
|
6
|
+
constructor(lockingPresenter: RecordLockingPresenter.Interface, original: ISingleEntryPresenter);
|
|
7
|
+
get vm(): ISingleEntryViewModel;
|
|
8
|
+
init(): Promise<void>;
|
|
9
|
+
save(): Promise<boolean>;
|
|
10
|
+
dispose(): void;
|
|
11
|
+
}
|
|
12
|
+
export declare const SingleEntryPresenterLockingDecorator: typeof SingleEntryPresenterWithLocking & {
|
|
13
|
+
__abstraction: import("@webiny/di").Abstraction<ISingleEntryPresenter>;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { computed, makeAutoObservable } from "mobx";
|
|
2
|
+
import { SingleEntryPresenter } from "@webiny/app-headless-cms/presentation/contentEntries/singleEntry/abstractions.js";
|
|
3
|
+
import { RecordLockingPresenter } from "./abstractions.js";
|
|
4
|
+
class SingleEntryPresenterWithLocking {
|
|
5
|
+
constructor(lockingPresenter, original){
|
|
6
|
+
this.lockingPresenter = lockingPresenter;
|
|
7
|
+
this.original = original;
|
|
8
|
+
makeAutoObservable(this, {
|
|
9
|
+
lockingPresenter: false,
|
|
10
|
+
original: false,
|
|
11
|
+
vm: computed
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
get vm() {
|
|
15
|
+
const base = this.original.vm;
|
|
16
|
+
const canEdit = this.lockingPresenter.vm.canEdit;
|
|
17
|
+
return {
|
|
18
|
+
...base,
|
|
19
|
+
canSave: base.canSave && canEdit
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async init() {
|
|
23
|
+
return this.original.init();
|
|
24
|
+
}
|
|
25
|
+
async save() {
|
|
26
|
+
return this.original.save();
|
|
27
|
+
}
|
|
28
|
+
dispose() {
|
|
29
|
+
return this.original.dispose();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const SingleEntryPresenterLockingDecorator = SingleEntryPresenter.createDecorator({
|
|
33
|
+
decorator: SingleEntryPresenterWithLocking,
|
|
34
|
+
dependencies: [
|
|
35
|
+
RecordLockingPresenter
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
export { SingleEntryPresenterLockingDecorator };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=SingleEntryPresenterDecorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/entryLocking/SingleEntryPresenterDecorator.js","sources":["../../../src/presentation/entryLocking/SingleEntryPresenterDecorator.ts"],"sourcesContent":["import { computed, makeAutoObservable } from \"mobx\";\nimport {\n SingleEntryPresenter,\n type ISingleEntryPresenter,\n type ISingleEntryViewModel\n} from \"@webiny/app-headless-cms/presentation/contentEntries/singleEntry/abstractions.js\";\nimport { RecordLockingPresenter } from \"./abstractions.js\";\n\nclass SingleEntryPresenterWithLocking implements ISingleEntryPresenter {\n constructor(\n private lockingPresenter: RecordLockingPresenter.Interface,\n private original: ISingleEntryPresenter\n ) {\n makeAutoObservable<SingleEntryPresenterWithLocking, \"lockingPresenter\" | \"original\">(this, {\n lockingPresenter: false,\n original: false,\n vm: computed\n });\n }\n\n get vm(): ISingleEntryViewModel {\n const base = this.original.vm;\n const canEdit = this.lockingPresenter.vm.canEdit;\n\n return {\n ...base,\n canSave: base.canSave && canEdit\n };\n }\n\n async init(): Promise<void> {\n return this.original.init();\n }\n\n async save(): Promise<boolean> {\n return this.original.save();\n }\n\n dispose(): void {\n return this.original.dispose();\n }\n}\n\nexport const SingleEntryPresenterLockingDecorator = SingleEntryPresenter.createDecorator({\n decorator: SingleEntryPresenterWithLocking,\n dependencies: [RecordLockingPresenter]\n});\n"],"names":["SingleEntryPresenterWithLocking","lockingPresenter","original","makeAutoObservable","computed","base","canEdit","SingleEntryPresenterLockingDecorator","SingleEntryPresenter","RecordLockingPresenter"],"mappings":";;;AAQA,MAAMA;IACF,YACYC,gBAAkD,EAClDC,QAA+B,CACzC;aAFUD,gBAAgB,GAAhBA;aACAC,QAAQ,GAARA;QAERC,mBAAqF,IAAI,EAAE;YACvF,kBAAkB;YAClB,UAAU;YACV,IAAIC;QACR;IACJ;IAEA,IAAI,KAA4B;QAC5B,MAAMC,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE;QAC7B,MAAMC,UAAU,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,OAAO;QAEhD,OAAO;YACH,GAAGD,IAAI;YACP,SAASA,KAAK,OAAO,IAAIC;QAC7B;IACJ;IAEA,MAAM,OAAsB;QACxB,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI;IAC7B;IAEA,MAAM,OAAyB;QAC3B,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI;IAC7B;IAEA,UAAgB;QACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO;IAChC;AACJ;AAEO,MAAMC,uCAAuCC,qBAAqB,eAAe,CAAC;IACrF,WAAWR;IACX,cAAc;QAACS;KAAuB;AAC1C"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IRecordLockingIdentity, IRecordLockingLockRecord } from "../../types.js";
|
|
2
|
+
export type RecordLockingStatus = "checking" | "locked" | "acquired" | "kicked-out" | "error" | null;
|
|
3
|
+
export interface IRecordLockingViewModel {
|
|
4
|
+
status: RecordLockingStatus;
|
|
5
|
+
lockRecord: IRecordLockingLockRecord | null;
|
|
6
|
+
lockedByUserName: string | null;
|
|
7
|
+
canForceUnlock: boolean;
|
|
8
|
+
canEdit: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface IKickOutData {
|
|
11
|
+
record: IRecordLockingLockRecord;
|
|
12
|
+
user: IRecordLockingIdentity;
|
|
13
|
+
}
|
|
14
|
+
export interface IRecordLockingPresenter {
|
|
15
|
+
readonly vm: IRecordLockingViewModel;
|
|
16
|
+
init(entryId: string, type: string): Promise<void>;
|
|
17
|
+
refreshLock(): Promise<void>;
|
|
18
|
+
forceUnlock(): Promise<boolean>;
|
|
19
|
+
handleKickOut(data: IKickOutData): void;
|
|
20
|
+
dispose(): void;
|
|
21
|
+
}
|
|
22
|
+
export declare const RecordLockingPresenter: import("@webiny/di").Abstraction<IRecordLockingPresenter>;
|
|
23
|
+
export declare namespace RecordLockingPresenter {
|
|
24
|
+
type Interface = IRecordLockingPresenter;
|
|
25
|
+
type ViewModel = IRecordLockingViewModel;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/entryLocking/abstractions.js","sources":["../../../src/presentation/entryLocking/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport type { IRecordLockingIdentity, IRecordLockingLockRecord } from \"~/types.js\";\n\nexport type RecordLockingStatus =\n | \"checking\"\n | \"locked\"\n | \"acquired\"\n | \"kicked-out\"\n | \"error\"\n | null;\n\nexport interface IRecordLockingViewModel {\n status: RecordLockingStatus;\n lockRecord: IRecordLockingLockRecord | null;\n lockedByUserName: string | null;\n canForceUnlock: boolean;\n canEdit: boolean;\n}\n\nexport interface IKickOutData {\n record: IRecordLockingLockRecord;\n user: IRecordLockingIdentity;\n}\n\nexport interface IRecordLockingPresenter {\n readonly vm: IRecordLockingViewModel;\n init(entryId: string, type: string): Promise<void>;\n refreshLock(): Promise<void>;\n forceUnlock(): Promise<boolean>;\n handleKickOut(data: IKickOutData): void;\n dispose(): void;\n}\n\nexport const RecordLockingPresenter =\n createAbstraction<IRecordLockingPresenter>(\"RecordLockingPresenter\");\n\nexport namespace RecordLockingPresenter {\n export type Interface = IRecordLockingPresenter;\n export type ViewModel = IRecordLockingViewModel;\n}\n"],"names":["RecordLockingPresenter","createAbstraction"],"mappings":";AAiCO,MAAMA,yBACTC,kBAA2C"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { IRecordLockingIdentity } from "../../../types.js";
|
|
3
|
+
interface ForceUnlockButtonProps {
|
|
4
|
+
canForceUnlock: boolean;
|
|
5
|
+
lockedBy: IRecordLockingIdentity | null;
|
|
6
|
+
entryTitle?: string;
|
|
7
|
+
onForceUnlock: () => Promise<boolean>;
|
|
8
|
+
onNavigateBack: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const ForceUnlockButton: ({ canForceUnlock, lockedBy, entryTitle, onForceUnlock, onNavigateBack }: ForceUnlockButtonProps) => React.JSX.Element | null;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import react, { useCallback } from "react";
|
|
2
|
+
import { Alert, Button, Text } from "@webiny/admin-ui";
|
|
3
|
+
import { useConfirmationDialog, useSnackbar } from "@webiny/app-admin";
|
|
4
|
+
const ForceUnlockButton = ({ canForceUnlock, lockedBy, entryTitle, onForceUnlock, onNavigateBack })=>{
|
|
5
|
+
const { showSnackbar } = useSnackbar();
|
|
6
|
+
const { showConfirmation } = useConfirmationDialog({
|
|
7
|
+
title: "Force unlock the entry",
|
|
8
|
+
message: /*#__PURE__*/ react.createElement("div", null, /*#__PURE__*/ react.createElement(Alert, {
|
|
9
|
+
type: "warning",
|
|
10
|
+
title: "Warning",
|
|
11
|
+
className: "mb-md"
|
|
12
|
+
}, /*#__PURE__*/ react.createElement("strong", null, lockedBy?.displayName || "Unknown user"), " is currently editing this record.", /*#__PURE__*/ react.createElement("br", null), "If you force unlock it, they could potentially lose their changes."), /*#__PURE__*/ react.createElement(Text, null, "You are about to forcefully unlock the ", /*#__PURE__*/ react.createElement("strong", null, entryTitle || "entry"), ". Are you sure you want to continue?"))
|
|
13
|
+
});
|
|
14
|
+
const onClick = useCallback(()=>{
|
|
15
|
+
showConfirmation(async ()=>{
|
|
16
|
+
const success = await onForceUnlock();
|
|
17
|
+
if (!success) return void showSnackbar("Failed to force unlock the entry.");
|
|
18
|
+
onNavigateBack();
|
|
19
|
+
});
|
|
20
|
+
}, [
|
|
21
|
+
onForceUnlock,
|
|
22
|
+
onNavigateBack
|
|
23
|
+
]);
|
|
24
|
+
if (!canForceUnlock) return null;
|
|
25
|
+
return /*#__PURE__*/ react.createElement("div", {
|
|
26
|
+
className: "mt-md"
|
|
27
|
+
}, /*#__PURE__*/ react.createElement(Text, {
|
|
28
|
+
as: "div",
|
|
29
|
+
className: "mb-md"
|
|
30
|
+
}, "Because you have full access to the system, you can force unlock the record."), /*#__PURE__*/ react.createElement(Button, {
|
|
31
|
+
onClick: onClick,
|
|
32
|
+
text: "Unlock and go back"
|
|
33
|
+
}));
|
|
34
|
+
};
|
|
35
|
+
export { ForceUnlockButton };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=ForceUnlockButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/entryLocking/components/ForceUnlockButton.js","sources":["../../../../src/presentation/entryLocking/components/ForceUnlockButton.tsx"],"sourcesContent":["import React, { useCallback } from \"react\";\nimport { Alert, Button, Text } from \"@webiny/admin-ui\";\nimport { useConfirmationDialog, useSnackbar } from \"@webiny/app-admin\";\nimport type { IRecordLockingIdentity } from \"~/types.js\";\n\ninterface ForceUnlockButtonProps {\n canForceUnlock: boolean;\n lockedBy: IRecordLockingIdentity | null;\n entryTitle?: string;\n onForceUnlock: () => Promise<boolean>;\n onNavigateBack: () => void;\n}\n\nexport const ForceUnlockButton = ({\n canForceUnlock,\n lockedBy,\n entryTitle,\n onForceUnlock,\n onNavigateBack\n}: ForceUnlockButtonProps) => {\n const { showSnackbar } = useSnackbar();\n const { showConfirmation } = useConfirmationDialog({\n title: \"Force unlock the entry\",\n message: (\n <div>\n <Alert type=\"warning\" title=\"Warning\" className={\"mb-md\"}>\n <strong>{lockedBy?.displayName || \"Unknown user\"}</strong> is currently editing\n this record.\n <br />\n If you force unlock it, they could potentially lose their changes.\n </Alert>\n <Text>\n You are about to forcefully unlock the <strong>{entryTitle || \"entry\"}</strong>.\n Are you sure you want to continue?\n </Text>\n </div>\n )\n });\n\n const onClick = useCallback(() => {\n showConfirmation(async () => {\n const success = await onForceUnlock();\n if (!success) {\n showSnackbar(\"Failed to force unlock the entry.\");\n return;\n }\n onNavigateBack();\n });\n }, [onForceUnlock, onNavigateBack]);\n\n if (!canForceUnlock) {\n return null;\n }\n\n return (\n <div className=\"mt-md\">\n <Text as={\"div\"} className={\"mb-md\"}>\n Because you have full access to the system, you can force unlock the record.\n </Text>\n <Button onClick={onClick} text={\"Unlock and go back\"} />\n </div>\n );\n};\n"],"names":["ForceUnlockButton","canForceUnlock","lockedBy","entryTitle","onForceUnlock","onNavigateBack","showSnackbar","useSnackbar","showConfirmation","useConfirmationDialog","Alert","Text","onClick","useCallback","success","Button"],"mappings":";;;AAaO,MAAMA,oBAAoB,CAAC,EAC9BC,cAAc,EACdC,QAAQ,EACRC,UAAU,EACVC,aAAa,EACbC,cAAc,EACO;IACrB,MAAM,EAAEC,YAAY,EAAE,GAAGC;IACzB,MAAM,EAAEC,gBAAgB,EAAE,GAAGC,sBAAsB;QAC/C,OAAO;QACP,SAAS,WAAT,GACI,oBAAC,2BACG,oBAACC,OAAKA;YAAC,MAAK;YAAU,OAAM;YAAU,WAAW;yBAC7C,oBAAC,gBAAQR,UAAU,eAAe,iBAAwB,oDAE1D,oBAAC,aAAK,qFAGV,oBAACS,MAAIA,MAAC,yDACqC,oBAAC,gBAAQR,cAAc,UAAiB;IAK/F;IAEA,MAAMS,UAAUC,YAAY;QACxBL,iBAAiB;YACb,MAAMM,UAAU,MAAMV;YACtB,IAAI,CAACU,SAAS,YACVR,aAAa;YAGjBD;QACJ;IACJ,GAAG;QAACD;QAAeC;KAAe;IAElC,IAAI,CAACJ,gBACD,OAAO;IAGX,OAAO,WAAP,GACI,oBAAC;QAAI,WAAU;qBACX,oBAACU,MAAIA;QAAC,IAAI;QAAO,WAAW;OAAS,+FAGrC,oBAACI,QAAMA;QAAC,SAASH;QAAS,MAAM;;AAG5C"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { IRecordLockingViewModel } from "../abstractions.js";
|
|
3
|
+
interface LockedEntryOverlayProps {
|
|
4
|
+
vm: IRecordLockingViewModel;
|
|
5
|
+
entryTitle?: string;
|
|
6
|
+
onForceUnlock: () => Promise<boolean>;
|
|
7
|
+
onNavigateBack: () => void;
|
|
8
|
+
}
|
|
9
|
+
export declare const LockedEntryOverlay: ({ vm, entryTitle, onForceUnlock, onNavigateBack }: LockedEntryOverlayProps) => React.JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import react from "react";
|
|
2
|
+
import { ReactComponent } from "@webiny/icons/lock.svg";
|
|
3
|
+
import { Grid, Heading, Icon, Text } from "@webiny/admin-ui";
|
|
4
|
+
import { ForceUnlockButton } from "./ForceUnlockButton.js";
|
|
5
|
+
const LockedEntryOverlay = ({ vm, entryTitle, onForceUnlock, onNavigateBack })=>{
|
|
6
|
+
const title = entryTitle ? `Record (${entryTitle}) is locked!` : "Record is locked!";
|
|
7
|
+
if (!vm.lockRecord?.lockedBy) return /*#__PURE__*/ react.createElement(Wrapper, null, /*#__PURE__*/ react.createElement(Heading, {
|
|
8
|
+
level: 4,
|
|
9
|
+
className: "mb-sm"
|
|
10
|
+
}, title), /*#__PURE__*/ react.createElement(Text, null, "This record is locked, but the system cannot find the user that created the record lock. A force-unlock is required to regain editing capabilities for this record."), /*#__PURE__*/ react.createElement(ForceUnlockButton, {
|
|
11
|
+
canForceUnlock: vm.canForceUnlock,
|
|
12
|
+
lockedBy: null,
|
|
13
|
+
entryTitle: entryTitle,
|
|
14
|
+
onForceUnlock: onForceUnlock,
|
|
15
|
+
onNavigateBack: onNavigateBack
|
|
16
|
+
}));
|
|
17
|
+
return /*#__PURE__*/ react.createElement(Wrapper, null, /*#__PURE__*/ react.createElement(Heading, {
|
|
18
|
+
level: 4,
|
|
19
|
+
className: "mb-sm"
|
|
20
|
+
}, title), /*#__PURE__*/ react.createElement(Text, null, "It is locked because ", /*#__PURE__*/ react.createElement("strong", null, vm.lockedByUserName), " is currently editing this record. You can either contact the user and ask them to unlock the record, or you can wait for the lock to expire."), /*#__PURE__*/ react.createElement(ForceUnlockButton, {
|
|
21
|
+
canForceUnlock: vm.canForceUnlock,
|
|
22
|
+
lockedBy: vm.lockRecord.lockedBy,
|
|
23
|
+
entryTitle: entryTitle,
|
|
24
|
+
onForceUnlock: onForceUnlock,
|
|
25
|
+
onNavigateBack: onNavigateBack
|
|
26
|
+
}));
|
|
27
|
+
};
|
|
28
|
+
const Wrapper = ({ children })=>/*#__PURE__*/ react.createElement("div", {
|
|
29
|
+
className: "w-5/12 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50"
|
|
30
|
+
}, /*#__PURE__*/ react.createElement("div", {
|
|
31
|
+
className: "flex p-lg border-sm border-neutral-dimmed-darker rounded-3xl bg-neutral-base"
|
|
32
|
+
}, /*#__PURE__*/ react.createElement(Grid, null, /*#__PURE__*/ react.createElement(Grid.Column, {
|
|
33
|
+
span: 3
|
|
34
|
+
}, /*#__PURE__*/ react.createElement("div", {
|
|
35
|
+
className: "h-full flex items-center justify-center bg-neutral-dimmed rounded-md p-lg"
|
|
36
|
+
}, /*#__PURE__*/ react.createElement(Icon, {
|
|
37
|
+
style: {
|
|
38
|
+
width: "64px",
|
|
39
|
+
height: "64px"
|
|
40
|
+
},
|
|
41
|
+
icon: /*#__PURE__*/ react.createElement(ReactComponent, null),
|
|
42
|
+
label: "Locked Record",
|
|
43
|
+
color: "accent",
|
|
44
|
+
size: "lg"
|
|
45
|
+
}))), /*#__PURE__*/ react.createElement(Grid.Column, {
|
|
46
|
+
span: 9
|
|
47
|
+
}, /*#__PURE__*/ react.createElement("div", {
|
|
48
|
+
className: "flex flex-col justify-center"
|
|
49
|
+
}, children)))));
|
|
50
|
+
export { LockedEntryOverlay };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=LockedEntryOverlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/entryLocking/components/LockedEntryOverlay.js","sources":["../../../../src/presentation/entryLocking/components/LockedEntryOverlay.tsx"],"sourcesContent":["import React from \"react\";\nimport { ReactComponent as LockIcon } from \"@webiny/icons/lock.svg\";\nimport { Grid, Heading, Icon, Text } from \"@webiny/admin-ui\";\nimport { ForceUnlockButton } from \"./ForceUnlockButton.js\";\nimport type { IRecordLockingViewModel } from \"../abstractions.js\";\n\ninterface LockedEntryOverlayProps {\n vm: IRecordLockingViewModel;\n entryTitle?: string;\n onForceUnlock: () => Promise<boolean>;\n onNavigateBack: () => void;\n}\n\nexport const LockedEntryOverlay = ({\n vm,\n entryTitle,\n onForceUnlock,\n onNavigateBack\n}: LockedEntryOverlayProps) => {\n const title = entryTitle ? `Record (${entryTitle}) is locked!` : \"Record is locked!\";\n\n if (!vm.lockRecord?.lockedBy) {\n return (\n <Wrapper>\n <Heading level={4} className={\"mb-sm\"}>\n {title}\n </Heading>\n <Text>\n This record is locked, but the system cannot find the user that created the\n record lock. A force-unlock is required to regain editing capabilities for this\n record.\n </Text>\n <ForceUnlockButton\n canForceUnlock={vm.canForceUnlock}\n lockedBy={null}\n entryTitle={entryTitle}\n onForceUnlock={onForceUnlock}\n onNavigateBack={onNavigateBack}\n />\n </Wrapper>\n );\n }\n\n return (\n <Wrapper>\n <Heading level={4} className={\"mb-sm\"}>\n {title}\n </Heading>\n <Text>\n It is locked because <strong>{vm.lockedByUserName}</strong> is currently editing\n this record. You can either contact the user and ask them to unlock the record, or\n you can wait for the lock to expire.\n </Text>\n <ForceUnlockButton\n canForceUnlock={vm.canForceUnlock}\n lockedBy={vm.lockRecord.lockedBy}\n entryTitle={entryTitle}\n onForceUnlock={onForceUnlock}\n onNavigateBack={onNavigateBack}\n />\n </Wrapper>\n );\n};\n\nconst Wrapper = ({ children }: { children: React.ReactNode }) => {\n return (\n <div className=\"w-5/12 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50\">\n <div\n className={\n \"flex p-lg border-sm border-neutral-dimmed-darker rounded-3xl bg-neutral-base\"\n }\n >\n <Grid>\n <Grid.Column span={3}>\n <div className=\"h-full flex items-center justify-center bg-neutral-dimmed rounded-md p-lg\">\n <Icon\n style={{ width: \"64px\", height: \"64px\" }}\n icon={<LockIcon />}\n label={\"Locked Record\"}\n color={\"accent\"}\n size={\"lg\"}\n />\n </div>\n </Grid.Column>\n <Grid.Column span={9}>\n <div className={\"flex flex-col justify-center\"}>{children}</div>\n </Grid.Column>\n </Grid>\n </div>\n </div>\n );\n};\n"],"names":["LockedEntryOverlay","vm","entryTitle","onForceUnlock","onNavigateBack","title","Wrapper","Heading","Text","ForceUnlockButton","children","Grid","Icon","LockIcon"],"mappings":";;;;AAaO,MAAMA,qBAAqB,CAAC,EAC/BC,EAAE,EACFC,UAAU,EACVC,aAAa,EACbC,cAAc,EACQ;IACtB,MAAMC,QAAQH,aAAa,CAAC,QAAQ,EAAEA,WAAW,YAAY,CAAC,GAAG;IAEjE,IAAI,CAACD,GAAG,UAAU,EAAE,UAChB,OAAO,WAAP,GACI,oBAACK,SAAOA,MAAAA,WAAAA,GACJ,oBAACC,SAAOA;QAAC,OAAO;QAAG,WAAW;OACzBF,QAAAA,WAAAA,GAEL,oBAACG,MAAIA,MAAC,sLAKN,oBAACC,mBAAiBA;QACd,gBAAgBR,GAAG,cAAc;QACjC,UAAU;QACV,YAAYC;QACZ,eAAeC;QACf,gBAAgBC;;IAMhC,OAAO,WAAP,GACI,oBAACE,SAAOA,MAAAA,WAAAA,GACJ,oBAACC,SAAOA;QAAC,OAAO;QAAG,WAAW;OACzBF,QAAAA,WAAAA,GAEL,oBAACG,MAAIA,MAAC,uCACmB,oBAAC,gBAAQP,GAAG,gBAAgB,GAAU,gKAI/D,oBAACQ,mBAAiBA;QACd,gBAAgBR,GAAG,cAAc;QACjC,UAAUA,GAAG,UAAU,CAAC,QAAQ;QAChC,YAAYC;QACZ,eAAeC;QACf,gBAAgBC;;AAIhC;AAEA,MAAME,UAAU,CAAC,EAAEI,QAAQ,EAAiC,GACjD,WAAP,GACI,oBAAC;QAAI,WAAU;qBACX,oBAAC;QACG,WACI;qBAGJ,oBAACC,MAAIA,MAAAA,WAAAA,GACD,oBAACA,KAAK,MAAM;QAAC,MAAM;qBACf,oBAAC;QAAI,WAAU;qBACX,oBAACC,MAAIA;QACD,OAAO;YAAE,OAAO;YAAQ,QAAQ;QAAO;QACvC,oBAAM,oBAACC,gBAAQA;QACf,OAAO;QACP,OAAO;QACP,MAAM;wBAIlB,oBAACF,KAAK,MAAM;QAAC,MAAM;qBACf,oBAAC;QAAI,WAAW;OAAiCD"}
|