Merge branch 'main' into feature/property-editor-configs-v3

This commit is contained in:
Mads Rasmussen
2023-01-10 18:08:33 +01:00
33 changed files with 625 additions and 598 deletions

View File

@@ -17,7 +17,7 @@
"@umbraco-ui/uui-modal-container": "file:umbraco-ui-uui-modal-container-0.0.0.tgz",
"@umbraco-ui/uui-modal-dialog": "file:umbraco-ui-uui-modal-dialog-0.0.0.tgz",
"@umbraco-ui/uui-modal-sidebar": "file:umbraco-ui-uui-modal-sidebar-0.0.0.tgz",
"element-internals-polyfill": "^1.1.17",
"element-internals-polyfill": "^1.1.18",
"lit": "^2.5.0",
"lodash": "^4.17.21",
"openapi-typescript-fetch": "^1.1.3",
@@ -29,7 +29,7 @@
"@babel/core": "^7.20.12",
"@mdx-js/react": "^2.2.1",
"@open-wc/testing": "^3.1.7",
"@playwright/test": "^1.29.1",
"@playwright/test": "^1.29.2",
"@storybook/addon-a11y": "^6.5.15",
"@storybook/addon-actions": "^6.5.14",
"@storybook/addon-essentials": "^6.5.15",
@@ -41,8 +41,8 @@
"@types/lodash-es": "^4.17.6",
"@types/mocha": "^10.0.0",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"@web/dev-server-esbuild": "^0.3.3",
"@web/dev-server-import-maps": "^0.0.7",
"@web/test-runner": "^0.15.0",
@@ -2897,13 +2897,13 @@
"dev": true
},
"node_modules/@playwright/test": {
"version": "1.29.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.29.1.tgz",
"integrity": "sha512-iQxk2DX5U9wOGV3+/Jh9OHPsw5H3mleUL2S4BgQuwtlAfK3PnKvn38m4Rg9zIViGHVW24opSm99HQm/UFLEy6w==",
"version": "1.29.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.29.2.tgz",
"integrity": "sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.29.1"
"playwright-core": "1.29.2"
},
"bin": {
"playwright": "cli.js"
@@ -2913,9 +2913,9 @@
}
},
"node_modules/@playwright/test/node_modules/playwright-core": {
"version": "1.29.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.1.tgz",
"integrity": "sha512-20Ai3d+lMkWpI9YZYlxk8gxatfgax5STW8GaMozAHwigLiyiKQrdkt7gaoT9UQR8FIVDg6qVXs9IoZUQrDjIIg==",
"version": "1.29.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.2.tgz",
"integrity": "sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA==",
"dev": true,
"bin": {
"playwright": "cli.js"
@@ -6375,14 +6375,14 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz",
"integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.1.tgz",
"integrity": "sha512-9nY5K1Rp2ppmpb9s9S2aBiF3xo5uExCehMDmYmmFqqyxgenbHJ3qbarcLt4ITgaD6r/2ypdlcFRdcuVPnks+fQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/type-utils": "5.48.0",
"@typescript-eslint/utils": "5.48.0",
"@typescript-eslint/scope-manager": "5.48.1",
"@typescript-eslint/type-utils": "5.48.1",
"@typescript-eslint/utils": "5.48.1",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -6423,14 +6423,14 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz",
"integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.1.tgz",
"integrity": "sha512-4yg+FJR/V1M9Xoq56SF9Iygqm+r5LMXvheo6DQ7/yUWynQ4YfCRnsKuRgqH4EQ5Ya76rVwlEpw4Xu+TgWQUcdA==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"@typescript-eslint/scope-manager": "5.48.1",
"@typescript-eslint/types": "5.48.1",
"@typescript-eslint/typescript-estree": "5.48.1",
"debug": "^4.3.4"
},
"engines": {
@@ -6450,13 +6450,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz",
"integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.1.tgz",
"integrity": "sha512-S035ueRrbxRMKvSTv9vJKIWgr86BD8s3RqoRZmsSh/s8HhIs90g6UlK8ZabUSjUZQkhVxt7nmZ63VJ9dcZhtDQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0"
"@typescript-eslint/types": "5.48.1",
"@typescript-eslint/visitor-keys": "5.48.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -6467,13 +6467,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz",
"integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.1.tgz",
"integrity": "sha512-Hyr8HU8Alcuva1ppmqSYtM/Gp0q4JOp1F+/JH5D1IZm/bUBrV0edoewQZiEc1r6I8L4JL21broddxK8HAcZiqQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "5.48.0",
"@typescript-eslint/utils": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.1",
"@typescript-eslint/utils": "5.48.1",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@@ -6494,9 +6494,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz",
"integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.1.tgz",
"integrity": "sha512-xHyDLU6MSuEEdIlzrrAerCGS3T7AA/L8Hggd0RCYBi0w3JMvGYxlLlXHeg50JI9Tfg5MrtsfuNxbS/3zF1/ATg==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -6507,13 +6507,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz",
"integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.1.tgz",
"integrity": "sha512-Hut+Osk5FYr+sgFh8J/FHjqX6HFcDzTlWLrFqGoK5kVUN3VBHF/QzZmAsIXCQ8T/W9nQNBTqalxi1P3LSqWnRA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0",
"@typescript-eslint/types": "5.48.1",
"@typescript-eslint/visitor-keys": "5.48.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -6549,16 +6549,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz",
"integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.1.tgz",
"integrity": "sha512-SmQuSrCGUOdmGMwivW14Z0Lj8dxG1mOFZ7soeJ0TQZEJcs3n5Ndgkg0A4bcMFzBELqLJ6GTHnEU+iIoaD6hFGA==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"@typescript-eslint/scope-manager": "5.48.1",
"@typescript-eslint/types": "5.48.1",
"@typescript-eslint/typescript-estree": "5.48.1",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
@@ -6590,12 +6590,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz",
"integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.1.tgz",
"integrity": "sha512-Ns0XBwmfuX7ZknznfXozgnydyR8F6ev/KEGePP4i74uL3ArsKbEhJ7raeKr1JSa997DBDwol/4a0Y+At82c9dA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/types": "5.48.1",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@@ -11810,9 +11810,9 @@
"dev": true
},
"node_modules/element-internals-polyfill": {
"version": "1.1.17",
"resolved": "https://registry.npmjs.org/element-internals-polyfill/-/element-internals-polyfill-1.1.17.tgz",
"integrity": "sha512-sMDJyJiwvcHB6wLnyG+y/9FRxi/9OyI8bmjyw18K6b5iVlBjmA5CJVTFz4K2I7R53yqevK8WkTrfBmSHJXH9Rw=="
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/element-internals-polyfill/-/element-internals-polyfill-1.1.18.tgz",
"integrity": "sha512-ULyzpzblTZfMPEt83NphWeREajgaKQBNSTXvNBcjTeriIy7GsuAHFUZ0CpHnlDIVdvPlWcewfu7n7vVfiifZlQ=="
},
"node_modules/element-resize-detector": {
"version": "1.2.4",
@@ -30476,19 +30476,19 @@
}
},
"@playwright/test": {
"version": "1.29.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.29.1.tgz",
"integrity": "sha512-iQxk2DX5U9wOGV3+/Jh9OHPsw5H3mleUL2S4BgQuwtlAfK3PnKvn38m4Rg9zIViGHVW24opSm99HQm/UFLEy6w==",
"version": "1.29.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.29.2.tgz",
"integrity": "sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==",
"dev": true,
"requires": {
"@types/node": "*",
"playwright-core": "1.29.1"
"playwright-core": "1.29.2"
},
"dependencies": {
"playwright-core": {
"version": "1.29.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.1.tgz",
"integrity": "sha512-20Ai3d+lMkWpI9YZYlxk8gxatfgax5STW8GaMozAHwigLiyiKQrdkt7gaoT9UQR8FIVDg6qVXs9IoZUQrDjIIg==",
"version": "1.29.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.2.tgz",
"integrity": "sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA==",
"dev": true
}
}
@@ -33057,14 +33057,14 @@
}
},
"@typescript-eslint/eslint-plugin": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz",
"integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.1.tgz",
"integrity": "sha512-9nY5K1Rp2ppmpb9s9S2aBiF3xo5uExCehMDmYmmFqqyxgenbHJ3qbarcLt4ITgaD6r/2ypdlcFRdcuVPnks+fQ==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/type-utils": "5.48.0",
"@typescript-eslint/utils": "5.48.0",
"@typescript-eslint/scope-manager": "5.48.1",
"@typescript-eslint/type-utils": "5.48.1",
"@typescript-eslint/utils": "5.48.1",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -33085,53 +33085,53 @@
}
},
"@typescript-eslint/parser": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz",
"integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.1.tgz",
"integrity": "sha512-4yg+FJR/V1M9Xoq56SF9Iygqm+r5LMXvheo6DQ7/yUWynQ4YfCRnsKuRgqH4EQ5Ya76rVwlEpw4Xu+TgWQUcdA==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"@typescript-eslint/scope-manager": "5.48.1",
"@typescript-eslint/types": "5.48.1",
"@typescript-eslint/typescript-estree": "5.48.1",
"debug": "^4.3.4"
}
},
"@typescript-eslint/scope-manager": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz",
"integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.1.tgz",
"integrity": "sha512-S035ueRrbxRMKvSTv9vJKIWgr86BD8s3RqoRZmsSh/s8HhIs90g6UlK8ZabUSjUZQkhVxt7nmZ63VJ9dcZhtDQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0"
"@typescript-eslint/types": "5.48.1",
"@typescript-eslint/visitor-keys": "5.48.1"
}
},
"@typescript-eslint/type-utils": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz",
"integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.1.tgz",
"integrity": "sha512-Hyr8HU8Alcuva1ppmqSYtM/Gp0q4JOp1F+/JH5D1IZm/bUBrV0edoewQZiEc1r6I8L4JL21broddxK8HAcZiqQ==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "5.48.0",
"@typescript-eslint/utils": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.1",
"@typescript-eslint/utils": "5.48.1",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/types": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz",
"integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.1.tgz",
"integrity": "sha512-xHyDLU6MSuEEdIlzrrAerCGS3T7AA/L8Hggd0RCYBi0w3JMvGYxlLlXHeg50JI9Tfg5MrtsfuNxbS/3zF1/ATg==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz",
"integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.1.tgz",
"integrity": "sha512-Hut+Osk5FYr+sgFh8J/FHjqX6HFcDzTlWLrFqGoK5kVUN3VBHF/QzZmAsIXCQ8T/W9nQNBTqalxi1P3LSqWnRA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0",
"@typescript-eslint/types": "5.48.1",
"@typescript-eslint/visitor-keys": "5.48.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -33151,16 +33151,16 @@
}
},
"@typescript-eslint/utils": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz",
"integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.1.tgz",
"integrity": "sha512-SmQuSrCGUOdmGMwivW14Z0Lj8dxG1mOFZ7soeJ0TQZEJcs3n5Ndgkg0A4bcMFzBELqLJ6GTHnEU+iIoaD6hFGA==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"@typescript-eslint/scope-manager": "5.48.1",
"@typescript-eslint/types": "5.48.1",
"@typescript-eslint/typescript-estree": "5.48.1",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
@@ -33178,12 +33178,12 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz",
"integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==",
"version": "5.48.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.1.tgz",
"integrity": "sha512-Ns0XBwmfuX7ZknznfXozgnydyR8F6ev/KEGePP4i74uL3ArsKbEhJ7raeKr1JSa997DBDwol/4a0Y+At82c9dA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/types": "5.48.1",
"eslint-visitor-keys": "^3.3.0"
}
},
@@ -37472,9 +37472,9 @@
"dev": true
},
"element-internals-polyfill": {
"version": "1.1.17",
"resolved": "https://registry.npmjs.org/element-internals-polyfill/-/element-internals-polyfill-1.1.17.tgz",
"integrity": "sha512-sMDJyJiwvcHB6wLnyG+y/9FRxi/9OyI8bmjyw18K6b5iVlBjmA5CJVTFz4K2I7R53yqevK8WkTrfBmSHJXH9Rw=="
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/element-internals-polyfill/-/element-internals-polyfill-1.1.18.tgz",
"integrity": "sha512-ULyzpzblTZfMPEt83NphWeREajgaKQBNSTXvNBcjTeriIy7GsuAHFUZ0CpHnlDIVdvPlWcewfu7n7vVfiifZlQ=="
},
"element-resize-detector": {
"version": "1.2.4",

View File

@@ -62,7 +62,7 @@
"@umbraco-ui/uui-modal-sidebar": "file:umbraco-ui-uui-modal-sidebar-0.0.0.tgz",
"@umbraco-ui/uui-color-swatches": "file:umbraco-ui-uui-color-swatches-2.0.0.tgz",
"@umbraco-ui/uui-color-swatch": "file:umbraco-ui-uui-color-swatch-0.0.0.tgz",
"element-internals-polyfill": "^1.1.17",
"element-internals-polyfill": "^1.1.18",
"lit": "^2.5.0",
"lodash": "^4.17.21",
"openapi-typescript-fetch": "^1.1.3",
@@ -74,7 +74,7 @@
"@babel/core": "^7.20.12",
"@mdx-js/react": "^2.2.1",
"@open-wc/testing": "^3.1.7",
"@playwright/test": "^1.29.1",
"@playwright/test": "^1.29.2",
"@storybook/addon-a11y": "^6.5.15",
"@storybook/addon-actions": "^6.5.14",
"@storybook/addon-essentials": "^6.5.15",
@@ -86,8 +86,8 @@
"@types/lodash-es": "^4.17.6",
"@types/mocha": "^10.0.0",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"@web/dev-server-esbuild": "^0.3.3",
"@web/dev-server-import-maps": "^0.0.7",
"@web/test-runner": "^0.15.0",

View File

@@ -3,7 +3,7 @@ import { UmbDataStoreBase } from '../../../core/stores/store';
import { ApiError, DocumentTypeResource, DocumentTypeTreeItem, ProblemDetails } from '@umbraco-cms/backend-api';
import type { DocumentTypeDetails } from '@umbraco-cms/models';
const isDocumentTypeDetails = (
export const isDocumentTypeDetails = (
documentType: DocumentTypeDetails | DocumentTypeTreeItem
): documentType is DocumentTypeDetails => {
return (documentType as DocumentTypeDetails).properties !== undefined;

View File

@@ -23,4 +23,9 @@ export class UmbWorkspaceDocumentTypeContext extends UmbWorkspaceContentContext<
constructor(host: UmbControllerHostInterface) {
super(host, DefaultDocumentTypeData, 'umbDocumentTypeStore', 'documentType');
}
public setPropertyValue(alias: string, value: unknown) {
throw new Error("setPropertyValue is not implemented for UmbWorkspaceDocumentTypeContext")
}
}

View File

@@ -3,7 +3,7 @@ import { UmbNodeStoreBase } from '../../../core/stores/store';
import type { DocumentDetails } from '@umbraco-cms/models';
import { ApiError, DocumentResource, DocumentTreeItem, FolderTreeItem, ProblemDetails } from '@umbraco-cms/backend-api';
const isDocumentDetails = (document: DocumentDetails | DocumentTreeItem): document is DocumentDetails => {
export const isDocumentDetails = (document: DocumentDetails | DocumentTreeItem): document is DocumentDetails => {
return (document as DocumentDetails).data !== undefined;
};

View File

@@ -1,7 +1,8 @@
import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context';
import { STORE_ALIAS } from 'src/backoffice/documents/documents/document.store';
import { isDocumentDetails, STORE_ALIAS as DOCUMENT_STORE_ALIAS } from 'src/backoffice/documents/documents/document.store';
import type { UmbDocumentStore, UmbDocumentStoreItemType } from 'src/backoffice/documents/documents/document.store';
import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin';
import type { DocumentDetails } from '@umbraco-cms/models';
const DefaultDocumentData = {
key: '',
@@ -34,11 +35,26 @@ const DefaultDocumentData = {
export class UmbWorkspaceDocumentContext extends UmbWorkspaceContentContext<UmbDocumentStoreItemType, UmbDocumentStore> {
constructor(host: UmbControllerHostInterface) {
super(host, DefaultDocumentData, STORE_ALIAS, 'document');
super(host, DefaultDocumentData, DOCUMENT_STORE_ALIAS, 'document');
}
public setPropertyValue(alias: string, value: unknown) {
const data = this.getData();
// TODO: make sure to check that we have a details model:
// TODO: make a Method to cast
if(isDocumentDetails(data)) {
const newDataSet = (data as DocumentDetails).data.map((entry) => {
if (entry.alias === alias) {
return {alias: alias, value: value};
}
return entry;
});
this.update({data: newDataSet} as Partial<UmbDocumentStoreItemType>);
}
}
/*
concept notes:
@@ -51,4 +67,4 @@ export class UmbWorkspaceDocumentContext extends UmbWorkspaceContentContext<UmbD
}
*/
}
}

View File

@@ -35,4 +35,8 @@ export class UmbWorkspaceMediaContext extends UmbWorkspaceContentContext<UmbMedi
constructor(host: UmbControllerHostInterface) {
super(host, DefaultMediaData, 'umbMediaStore', 'media');
}
public setPropertyValue(alias: string, value: unknown) {
throw new Error("setPropertyValue is not implemented for UmbWorkspaceMediaContext")
}
}

View File

@@ -30,8 +30,8 @@ export class UmbDashboardPerformanceProfilingElement extends UmbLitElement {
@state()
private _profilingPerformance = false;
connectedCallback(): void {
super.connectedCallback();
constructor() {
super();
this._getProfilingStatus();
this._profilingPerformance = localStorage.getItem('profilingPerformance') === 'true';
}

View File

@@ -10,6 +10,11 @@ const isDataTypeDetails = (dataType: DataTypeDetails | FolderTreeItem): dataType
// TODO: can we make is easy to reuse store methods across different stores?
export type UmbDataTypeStoreItemType = DataTypeDetails | FolderTreeItem;
// TODO: research how we write names of global consts.
export const STORE_ALIAS = 'umbDataTypeStore';
/**
* @export
* @class UmbDataTypesStore
@@ -18,7 +23,7 @@ export type UmbDataTypeStoreItemType = DataTypeDetails | FolderTreeItem;
*/
export class UmbDataTypeStore extends UmbDataStoreBase<UmbDataTypeStoreItemType> {
public readonly storeAlias = 'umbDataTypeStore';
public readonly storeAlias = STORE_ALIAS;
/**
* @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable.

View File

@@ -2,14 +2,13 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html } from 'lit';
import { ifDefined } from 'lit-html/directives/if-defined.js';
import { customElement, property, state } from 'lit/decorators.js';
import { EMPTY, of, switchMap } from 'rxjs';
import { UmbDataTypeStore } from '../../../settings/data-types/data-type.store';
import type { ContentProperty, ManifestTypes } from '@umbraco-cms/models';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
import type { ContentProperty, DataTypeDetails } from '@umbraco-cms/models';
import '../entity-property/entity-property.element';
import '../workspace-property/workspace-property.element';
import { UmbLitElement } from '@umbraco-cms/element';
import { UmbObserverController } from '@umbraco-cms/observable-api';
@customElement('umb-content-property')
export class UmbContentPropertyElement extends UmbLitElement {
@@ -22,18 +21,22 @@ export class UmbContentPropertyElement extends UmbLitElement {
`,
];
// TODO: Consider if we just need to get the DataType Key?..
private _property?: ContentProperty;
@property({ type: Object, attribute: false })
public get property(): ContentProperty | undefined {
return this._property;
}
public set property(value: ContentProperty | undefined) {
const oldProperty = this._property;
this._property = value;
this._observeDataType();
if(this._property?.dataTypeKey !== oldProperty?.dataTypeKey) {
this._observeDataType(this._property?.dataTypeKey);
}
}
@property()
value?: string;
value?: object | string;
@state()
private _propertyEditorUIAlias?: string;
@@ -42,43 +45,40 @@ export class UmbContentPropertyElement extends UmbLitElement {
private _dataTypeData?: any;
private _dataTypeStore?: UmbDataTypeStore;
private _dataTypeObserver?: UmbObserverController<DataTypeDetails | null>;
constructor() {
super();
this.consumeContext('umbDataTypeStore', (instance) => {
this._dataTypeStore = instance;
this._observeDataType();
this._observeDataType(this._property?.dataTypeKey);
});
}
private _observeDataType() {
if (!this._dataTypeStore || !this._property) return;
private _observeDataType(dataTypeKey?: string) {
if (!this._dataTypeStore) return;
this.observe(
this._dataTypeStore.getByKey(this._property.dataTypeKey).pipe(
switchMap((dataType) => {
if (!dataType?.propertyEditorUIAlias) return EMPTY;
this._dataTypeData = dataType.data;
return umbExtensionsRegistry.getByAlias(dataType.propertyEditorUIAlias) ?? of(null);
})
),
(manifest) => {
if (manifest?.type === 'propertyEditorUI') {
this._propertyEditorUIAlias = manifest.alias;
this._dataTypeObserver?.destroy();
if(dataTypeKey) {
this._dataTypeObserver = this.observe(
this._dataTypeStore.getByKey(dataTypeKey),
(dataType) => {
this._dataTypeData = dataType?.data;
this._propertyEditorUIAlias = dataType?.propertyEditorUIAlias || undefined;
}
}
);
);
}
}
render() {
return html`<umb-entity-property
label=${ifDefined(this.property?.label)}
description=${ifDefined(this.property?.description)}
alias="${ifDefined(this.property?.alias)}"
return html`<umb-workspace-property
alias=${ifDefined(this._property?.alias)}
label=${ifDefined(this._property?.label)}
description=${ifDefined(this._property?.description)}
property-editor-ui-alias="${ifDefined(this._propertyEditorUIAlias)}"
.value="${this.value}"
.config="${this._dataTypeData}"></umb-entity-property>`;
.value=${this.value}
.config=${this._dataTypeData}></umb-workspace-property>`;
}
}

View File

@@ -1,20 +0,0 @@
import { Meta, Story } from '@storybook/web-components';
import { html } from 'lit-html';
import type { UmbEntityPropertyElement } from './entity-property.element';
import './entity-property.element';
export default {
title: 'Components/Entity Property',
component: 'umb-entity-property',
id: 'umb-entity-property',
} as Meta;
export const AAAOverview: Story<UmbEntityPropertyElement> = () =>
html` <umb-entity-property
label="Property"
description="Description"
alias="textProperty"
property-editor-ui-alias="Umb.PropertyEditorUI.TextBox"
.value="${'Hello'}"></umb-entity-property>`;
AAAOverview.storyName = 'Overview';

View File

@@ -1,64 +0,0 @@
import { BehaviorSubject, Observable } from "rxjs";
export type WorkspacePropertyData<ValueType> = {
alias?: string | null;
label?: string | null;
value?: ValueType | null;
};
export class UmbWorkspacePropertyContext<ValueType> {
private _data: BehaviorSubject<WorkspacePropertyData<ValueType>>;
public readonly data: Observable<WorkspacePropertyData<ValueType>>;
#defaultValue!: ValueType | null;
constructor(defaultValue: ValueType | null) {
this.#defaultValue = defaultValue;
// TODO: How do we connect this value with parent context?
// Ensuring the property editor value-property is updated...
this._data = new BehaviorSubject({value: defaultValue} as WorkspacePropertyData<ValueType>);
this.data = this._data.asObservable();
}
/*
hostConnected() {
}
hostDisconnected() {
}
*/
public getData() {
return this._data.getValue();
}
public update(data: Partial<WorkspacePropertyData<ValueType>>) {
this._data.next({ ...this.getData(), ...data });
}
public resetValue() {
console.log("property context reset")
this.update({value: this.#defaultValue})
}
// TODO: how can we make sure to call this.
public destroy(): void {
this._data.unsubscribe();
}
}

View File

@@ -0,0 +1,89 @@
import { UmbWorkspaceContentContext } from "../workspace/workspace-content/workspace-content.context";
import type { DataTypeDetails } from "@umbraco-cms/models";
import { UmbControllerHostInterface } from "src/core/controller/controller-host.mixin";
import { CreateObservablePart, UniqueBehaviorSubject } from "src/core/observable-api/unique-behavior-subject";
import { UmbContextProviderController } from "src/core/context-api/provide/context-provider.controller";
import { UmbContextConsumerController } from "src/core/context-api/consume/context-consumer.controller";
// If we get this from the server then we can consider using TypeScripts Partial<> around the model from the Management-API.
export type WorkspacePropertyData<ValueType> = {
alias?: string;
label?: string;
description?: string;
value?: ValueType | null;
config?: DataTypeDetails['data'];// This could potentially then come from hardcoded JS object and not the DataType store.
};
export class UmbWorkspacePropertyContext<ValueType = unknown> {
private _providerController: UmbContextProviderController;
private _data = new UniqueBehaviorSubject<WorkspacePropertyData<ValueType>>({});
public readonly alias = CreateObservablePart(this._data, data => data.alias);
public readonly label = CreateObservablePart(this._data, data => data.label);
public readonly description = CreateObservablePart(this._data, data => data.description);
public readonly value = CreateObservablePart(this._data, data => data.value);
public readonly config = CreateObservablePart(this._data, data => data.config);
private _workspaceContext?: UmbWorkspaceContentContext;
constructor(host:UmbControllerHostInterface) {
new UmbContextConsumerController(host, 'umbWorkspaceContext', (workspaceContext) => {
this._workspaceContext = workspaceContext;
});
this._providerController = new UmbContextProviderController(host, 'umbPropertyContext', this);
}
public setAlias(alias: WorkspacePropertyData<ValueType>['alias']) {
this._data.update({alias: alias});
}
public setLabel(label: WorkspacePropertyData<ValueType>['label']) {
this._data.update({label: label});
}
public setDescription(description: WorkspacePropertyData<ValueType>['description']) {
this._data.update({description: description});
}
public setValue(value: WorkspacePropertyData<ValueType>['value']) {
if(value === this._data.getValue().value) return;
this._data.update({value: value});
const alias = this._data.getValue().alias;
if(alias) {
this._workspaceContext?.setPropertyValue(alias, value);
}
}
public setConfig(config: WorkspacePropertyData<ValueType>['config']) {
this._data.update({config: config});
}
public resetValue() {
this.setValue(null);// TODO: Consider if this can be configured/provided from Property Editor or DataType Configuration or even locally specified in DocumentType.
}
// TODO: how can we make sure to call this.
public destroy(): void {
this._data.unsubscribe();
this._providerController.destroy(); // This would also be handled by the controller host, but if someone wanted to replace/remove this context without the host being destroyed. Then we have clean up out selfs here.
}
}

View File

@@ -1,10 +1,11 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, PropertyValueMap } from 'lit';
import { css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { ifDefined } from 'lit-html/directives/if-defined.js';
import { UmbWorkspacePropertyContext } from './workspace-property.context';
import { createExtensionElement } from '@umbraco-cms/extensions-api';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
import type { ManifestPropertyEditorUI, ManifestTypes } from '@umbraco-cms/models';
import type { DataTypePropertyData, ManifestPropertyEditorUI, ManifestTypes } from '@umbraco-cms/models';
import '../../property-actions/shared/property-action-menu/property-action-menu.element';
import 'src/backoffice/shared/components/workspace/workspace-property-layout/workspace-property-layout.element';
@@ -12,14 +13,13 @@ import { UmbObserverController } from 'src/core/observable-api/observer.controll
import { UmbLitElement } from '@umbraco-cms/element';
/**
* @element umb-entity-property
* @element umb-workspace-property
* @description - Component for displaying a entity property. The Element will render a Property Editor based on the Property Editor UI alias passed to the element.
* The element will also render all Property Actions related to the Property Editor.
*/
// TODO: get rid of the other mixins:
@customElement('umb-entity-property')
export class UmbEntityPropertyElement extends UmbLitElement {
@customElement('umb-workspace-property')
export class UmbWorkspacePropertyElement extends UmbLitElement {
static styles = [
UUITextStyles,
css`
@@ -48,6 +48,12 @@ export class UmbEntityPropertyElement extends UmbLitElement {
`,
];
@state()
private _label?:string;
@state()
private _description?:string;
/**
* Label. Name of the property
* @type {string}
@@ -55,7 +61,9 @@ export class UmbEntityPropertyElement extends UmbLitElement {
* @default ''
*/
@property({ type: String })
public label = '';
public set label(label: string) {
this._propertyContext.setLabel(label);
}
/**
* Description: render a description underneath the label.
@@ -64,7 +72,9 @@ export class UmbEntityPropertyElement extends UmbLitElement {
* @default ''
*/
@property({ type: String })
public description = '';
public set description(description: string) {
this._propertyContext.setDescription(description);
}
/**
* Alias
@@ -74,7 +84,9 @@ export class UmbEntityPropertyElement extends UmbLitElement {
* @default ''
*/
@property({ type: String })
public alias = '';
public set alias(alias: string) {
this._propertyContext.setAlias(alias);
}
/**
* Property Editor UI Alias. Render the Property Editor UI registered for this alias.
@@ -85,10 +97,8 @@ export class UmbEntityPropertyElement extends UmbLitElement {
*/
private _propertyEditorUIAlias = '';
@property({ type: String, attribute: 'property-editor-ui-alias' })
public get propertyEditorUIAlias(): string {
return this._propertyEditorUIAlias;
}
public set propertyEditorUIAlias(value: string) {
if(this._propertyEditorUIAlias === value) return;
this._propertyEditorUIAlias = value;
this._observePropertyEditorUI();
}
@@ -96,12 +106,14 @@ export class UmbEntityPropertyElement extends UmbLitElement {
/**
* Property Editor UI Alias. Render the Property Editor UI registered for this alias.
* @public
* @type {string}
* @type {unknown}
* @attr
* @default ''
* @default undefined
*/
@property({ type: Object, attribute: false })
public value?: any;
@property({attribute: false })
public set value(value: unknown) {
this._propertyContext.setValue(value);
}
/**
* Config. Configuration to pass to the Property Editor UI. This is also the configuration data stored on the Data Type.
@@ -111,82 +123,93 @@ export class UmbEntityPropertyElement extends UmbLitElement {
* @default ''
*/
@property({ type: Object, attribute: false })
public config?: any;
public set config(value: DataTypePropertyData[]) {
this._propertyContext.setConfig(value);
}
// TODO: make interface for UMBPropertyEditorElement
@state()
private _element?: { value?: any; config?: any } & HTMLElement; // TODO: invent interface for propertyEditorUI.
// TODO: How to get proper default value?
private _propertyContext = new UmbWorkspacePropertyContext<string>("");
private _propertyContext = new UmbWorkspacePropertyContext(this);
private propertyEditorUIObserver?: UmbObserverController<ManifestTypes>;
private _valueObserver?: UmbObserverController<unknown>;
private _configObserver?: UmbObserverController<unknown>;
constructor() {
super();
this.provideContext('umbPropertyContext', this._propertyContext);
this._observePropertyEditorUI();
this.addEventListener('property-editor-change', this._onPropertyEditorChange as any as EventListener);
this.observe(this._propertyContext.label, (label) => {
this._label = label;
});
this.observe(this._propertyContext.label, (description) => {
this._description = description;
});
}
private _onPropertyEditorChange = (e: CustomEvent) => {
const target = e.composedPath()[0] as any;
this.value = target.value;// Sets value in context.
e.stopPropagation();
};
private _observePropertyEditorUI() {
this.propertyEditorUIObserver?.destroy();
this.propertyEditorUIObserver = this.observe(umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUI', this.propertyEditorUIAlias), (manifest) => {
this._gotEditor(manifest);
this.propertyEditorUIObserver = this.observe(umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUI', this._propertyEditorUIAlias), (manifest) => {
this._gotEditorUI(manifest);
});
}
private _gotEditor(propertyEditorUIManifest?: ManifestPropertyEditorUI | null) {
if (!propertyEditorUIManifest) {
// TODO: if dataTypeKey didn't exist in store, we should do some nice UI.
private _gotEditorUI(manifest?: ManifestPropertyEditorUI | null) {
if (!manifest) {
// TODO: if propertyEditorUIAlias didn't exist in store, we should do some nice fail UI.
return;
}
createExtensionElement(propertyEditorUIManifest)
createExtensionElement(manifest)
.then((el) => {
const oldValue = this._element;
oldValue?.removeEventListener('change', this._onPropertyEditorChange as any as EventListener);
this._element = el;
if (this._element) {
this._element.value = this.value; // Be aware its duplicated code
this._element.config = this.config; // Be aware its duplicated code
this._valueObserver?.destroy();
this._configObserver?.destroy();
if(this._element) {
this._element.addEventListener('change', this._onPropertyEditorChange as any as EventListener);
this._valueObserver = this.observe(this._propertyContext.value, (value) => {
if(this._element) {
this._element.value = value;
}
});
this._configObserver = this.observe(this._propertyContext.config, (config) => {
if(this._element) {
this._element.config = config;
}
});
}
this.requestUpdate('element', oldValue);
})
.catch(() => {
// TODO: loading JS failed so we should do some nice UI. (This does only happen if extension has a js prop, otherwise we concluded that no source was needed resolved the load.)
});
}
private _onPropertyEditorChange = (e: CustomEvent) => {
const target = e.composedPath()[0] as any;
this.value = target.value;
this.dispatchEvent(new CustomEvent('property-value-change', { bubbles: true, composed: true }));
e.stopPropagation();
};
/** Lit does not currently handle dynamic tag names, therefor we are doing some manual rendering */
// TODO: Refactor into a base class for dynamic-tag element? we will be using this a lot for extensions.
// This could potentially hook into Lit and parse all properties defined in the specific class on to the dynamic-element. (see static elementProperties: PropertyDeclarationMap;)
willUpdate(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
super.willUpdate(changedProperties);
if (changedProperties.has('value') && this._element) {
this._element.value = this.value; // Be aware its duplicated code
}
if (changedProperties.has('config') && this._element) {
this._element.config = this.config; // Be aware its duplicated code
}
}
render() {
return html`
<umb-workspace-property-layout id="layout" label="${this.label}" description="${this.description}">
<umb-workspace-property-layout id="layout" label="${ifDefined(this._label)}" description="${ifDefined(this._description)}">
${this._renderPropertyActionMenu()}
<div slot="editor">${this._element}</div>
</umb-workspace-property-layout>
@@ -194,11 +217,11 @@ export class UmbEntityPropertyElement extends UmbLitElement {
}
private _renderPropertyActionMenu() {
return html`${this.propertyEditorUIAlias
return html`${this._propertyEditorUIAlias
? html`<umb-property-action-menu
slot="property-action-menu"
id="property-action-menu"
.propertyEditorUIAlias="${this.propertyEditorUIAlias}"
.propertyEditorUIAlias="${this._propertyEditorUIAlias}"
.value="${this.value}"></umb-property-action-menu>`
: ''}`;
}
@@ -206,6 +229,6 @@ export class UmbEntityPropertyElement extends UmbLitElement {
declare global {
interface HTMLElementTagNameMap {
'umb-entity-property': UmbEntityPropertyElement;
'umb-workspace-property': UmbWorkspacePropertyElement;
}
}

View File

@@ -0,0 +1,20 @@
import { Meta, Story } from '@storybook/web-components';
import { html } from 'lit-html';
import type { UmbWorkspacePropertyElement } from './workspace-property.element';
import './workspace-property.element';
export default {
title: 'Components/Entity Property',
component: 'umb-workspace-property',
id: 'umb-workspace-property',
} as Meta;
export const AAAOverview: Story<UmbWorkspacePropertyElement> = () =>
html` <umb-workspace-property
label="Property"
description="Description"
alias="textProperty"
property-editor-ui-alias="Umb.PropertyEditorUI.TextBox"
.value="${'Hello'}"></umb-workspace-property>`;
AAAOverview.storyName = 'Overview';

View File

@@ -2,6 +2,7 @@ import { css, html } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, state } from 'lit/decorators.js';
import { distinctUntilChanged } from 'rxjs';
import { repeat } from 'lit/directives/repeat.js';
import type { UmbWorkspaceContentContext } from '../../workspace-content.context';
import type { ContentProperty, ContentPropertyData, DocumentDetails, MediaDetails } from '@umbraco-cms/models';
@@ -40,11 +41,22 @@ export class UmbWorkspaceViewContentEditElement extends UmbLitElement {
private _observeContent() {
if (!this._workspaceContext) return;
/*
TODO: Property-Context: This observer gets all changes, We need to fix this. it should be simpler.
It should look at length and aliases? as long as they are identical nothing should change.
As they would update them selfs?
Should use a Observable for this._workspaceContext.properties
*/
this.observe(
this._workspaceContext.data.pipe(distinctUntilChanged()),
(content) => {
this._properties = content.properties;
this._data = content.data;
/*
Maybe we should not give the value, but the umb-content-property should get the context and observe its own data.
This would become a more specific Observer therefor better performance?.. Note to self: Debate with Mads how he sees this perspective.
*/
}
);
}
@@ -52,12 +64,14 @@ export class UmbWorkspaceViewContentEditElement extends UmbLitElement {
render() {
return html`
<uui-box>
${this._properties?.map(
(property: ContentProperty) => html`
<umb-content-property
${repeat(
this._properties,
(property) => property.alias,
(property) =>
html`<umb-content-property
.property=${property}
.value=${this._data.find((data) => data.alias === property.alias)?.value}></umb-content-property>
`
`
)}
</uui-box>
`;

View File

@@ -1,20 +1,27 @@
import { v4 as uuidv4 } from 'uuid';
import { BehaviorSubject, Observable } from 'rxjs';
import { UmbNotificationService } from '../../../../../core/notification';
import { UmbNotificationDefaultData } from '../../../../../core/notification/layouts/default';
import { UmbWorkspaceContext } from '../workspace-context/workspace.context';
import { UmbNodeStoreBase } from '@umbraco-cms/stores/store';
import { ContentTreeItem } from '@umbraco-cms/backend-api';
import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin';
import { UmbContextConsumerController } from 'src/core/context-api/consume/context-consumer.controller';
import { UmbObserverController } from '@umbraco-cms/observable-api';
import { UmbContextProviderController } from 'src/core/context-api/provide/context-provider.controller';
import { EntityTreeItem } from '@umbraco-cms/backend-api';
// TODO: Consider if its right to have this many class-inheritance of WorkspaceContext
// TODO: Could we extract this code into a 'Manager' of its own, which will be instantiated by the concrete Workspace Context. This will be more transparent and 'reuseable'
export class UmbWorkspaceContentContext<
ContentTypeType extends ContentTreeItem = ContentTreeItem,
export abstract class UmbWorkspaceContentContext<
ContentTypeType extends EntityTreeItem = EntityTreeItem,
StoreType extends UmbNodeStoreBase<ContentTypeType> = UmbNodeStoreBase<ContentTypeType>
> extends UmbWorkspaceContext<ContentTypeType> {
> {
protected _host: UmbControllerHostInterface;
// TODO: figure out how fine grained we want to make our observables.
// TODO: add interface
protected _data!:BehaviorSubject<ContentTypeType>;
public readonly data: Observable<ContentTypeType>;
protected _notificationService?: UmbNotificationService;
@@ -32,7 +39,14 @@ export class UmbWorkspaceContentContext<
storeAlias: string,
entityType: string
) {
super(host, defaultData);
this._host = host;
//TODO: Use the UniqueBehaviorSubject, and separate observables for each part?
this._data = new BehaviorSubject<ContentTypeType>(defaultData);
this.data = this._data.asObservable();
this.entityType = entityType;
new UmbContextConsumerController(
host,
@@ -42,25 +56,31 @@ export class UmbWorkspaceContentContext<
}
);
this.entityType = entityType;
new UmbContextConsumerController(host, storeAlias, (_instance: StoreType) => {
this._store = _instance;
if (!this._store) {
// TODO: make sure to break the application in a good way.
return;
}
this._readyToLoad();
this._observeStore();
// TODO: first provide when we have umbNotificationService as well.
new UmbContextProviderController(this._host, 'umbWorkspaceContext', this);
});
}
public getData() {
return this._data.getValue();
}
public update(data: Partial<ContentTypeType>) {
this._data.next({ ...this.getData(), ...data });
}
load(entityKey: string) {
this.#isNew = false;
this.entityKey = entityKey;
this._readyToLoad();
this._observeStore();
}
create(parentKey: string | null) {
@@ -69,14 +89,14 @@ export class UmbWorkspaceContentContext<
console.log("I'm new, and I will be created under ", parentKey)
}
protected _readyToLoad(): void {
protected _observeStore(): void {
if(!this._store || !this.entityKey) {
return;
}
if(!this.#isNew) {
this._storeSubscription?.destroy();
this._storeSubscription = new UmbObserverController(this._host, this._store.getByKey(this.entityKey),
this._storeSubscription = new UmbObserverController(this._host, this._store.getByKey(this.entityKey),
(content) => {
if (!content) return; // TODO: Handle nicely if there is no content data.
this.update(content as any);
@@ -88,6 +108,9 @@ export class UmbWorkspaceContentContext<
return this._store;
}
abstract setPropertyValue(alias: string, value: unknown):void;
public save(): Promise<void> {
if(!this._store) {
// TODO: more beautiful error:
@@ -104,4 +127,11 @@ export class UmbWorkspaceContentContext<
this._notificationService?.peek('danger', { data });
});
}
}
// TODO: how can we make sure to call this.
public destroy(): void {
this._data.unsubscribe();
}
}

View File

@@ -1,9 +1,6 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { distinctUntilChanged } from 'rxjs';
import type { UmbWorkspaceContentContext } from './workspace-content.context';
import type { DocumentDetails, MediaDetails } from '@umbraco-cms/models';
import { customElement, property } from 'lit/decorators.js';
import '../workspace-layout/workspace-layout.element';
import '../../variant-selector/variant-selector.element';
@@ -12,11 +9,8 @@ import '../../variant-selector/variant-selector.element';
// TODO: Make this dynamic, use load-extensions method to loop over extensions for this node.
import './views/edit/workspace-view-content-edit.element';
import './views/info/workspace-view-content-info.element';
import type { UmbNodeStoreBase } from '@umbraco-cms/stores/store';
import { UmbLitElement } from '@umbraco-cms/element';
type ContentTypeTypes = DocumentDetails | MediaDetails;
/**
* TODO: IMPORTANT TODO: Get rid of the content workspace. Instead we aim to get separate components that can be composed by each workspace.
* Example. Document Workspace would use a Variant-component(variant component would talk directly to the workspace-context)
@@ -49,52 +43,6 @@ export class UmbWorkspaceContentElement extends UmbLitElement {
@property()
alias!: string;
// TODO: use a NodeDetails type here:
@state()
_content?: ContentTypeTypes;
private _workspaceContext?: UmbWorkspaceContentContext<ContentTypeTypes, UmbNodeStoreBase<ContentTypeTypes>>;
constructor() {
super();
this.consumeContext('umbWorkspaceContext', (instance) => {
this._workspaceContext = instance;
this._observeWorkspace();
});
this.addEventListener('property-value-change', this._onPropertyValueChange);
}
private async _observeWorkspace() {
if (!this._workspaceContext) return;
this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (data) => {
this._content = data;
});
}
private _onPropertyValueChange = (e: Event) => {
const target = e.composedPath()[0] as any;
// TODO: Set value.
const property = this._content?.properties.find((x) => x.alias === target.alias);
if (property) {
this._setPropertyValue(property.alias, target.value);
} else {
console.error('property was not found', target.alias);
}
};
// TODO: How do we ensure this is a change of this document and not nested documents? Should the event be stopped at this spot at avoid such.
private _setPropertyValue(alias: string, value: unknown) {
this._content?.data.forEach((data) => {
if (data.alias === alias) {
data.value = value;
}
});
}
render() {
return html`
<umb-workspace-layout alias=${this.alias}>

View File

@@ -1,131 +0,0 @@
CollectionContext {
// RxJS store..
this._data = new BehaviorSubject<BlockType>(defaultData);
this.data = this._data.asObservable();
getStore();
}
DocumentCollectionContext extends CollectionContext {
// RxJS store..
this._data = new BehaviorSubject<BlockType>(defaultData);
this.data = this._data.asObservable();
getStore();
publish();
update();
}
MediaCollectionContext extends CollectionContext {
// RxJS store..
this._data = new BehaviorSubject<BlockType>(defaultData);
this.data = this._data.asObservable();
getStore();
update();
save();
}
VendrCollectionContext extends CollectionContext {
// RxJS store..
this._data = new BehaviorSubject<BlockType>(defaultData);
this.data = this._data.asObservable();
getStore();
update();
save();
}
DocumentContext {
// Validation?
// RxJS store..
this._data = new BehaviorSubject<BlockType>(defaultData);
this.data = this._data.asObservable();
getStore();
setPublishDate()
addVariant(name) {
this.data.name = name;
}
save() {
this.backendStore.save()
}
}
PropertyContext {
// Validation?
}
BlockContext {
// Validation?
this._data = new BehaviorSubject<BlockType>(defaultData);
this.data = this._data.asObservable();
this._liveEditing = true;
setName(name) {
this.update({name: name})
if(this._liveEditing) {
this.save();
}
}
save() {
//
this.parentData.block[123] = this.data.getData();
this.parentDatarxJS.update(this.parentData);
}
}
var myBlockContext = new BlockContext(documentData.blocks[1]);
// Property Editor Edit Element Name:
myBlockContext.data.subscribe((blockData) => {
this.input.value = blockData.name;
})
this.input.addEventListener("change", () => {
myBlockContext.setName(this.input.value);
// RXJS update?? ^^
})
blockContext.setName('sdaafgdss');
// Does does other update?

View File

@@ -1,40 +0,0 @@
import { BehaviorSubject, Observable } from "rxjs";
import { UmbControllerHostInterface } from "src/core/controller/controller-host.mixin";
export abstract class UmbWorkspaceContext<DataType> {
protected _host: UmbControllerHostInterface;
// TODO: figure out how fine grained we want to make our observables.
// TODO: add interface
protected _data!:BehaviorSubject<DataType>;
public readonly data: Observable<DataType>;
constructor(host:UmbControllerHostInterface, defaultData: DataType) {
this._host = host;
this._data = new BehaviorSubject<DataType>(defaultData);
this.data = this._data.asObservable();
}
public getData() {
return this._data.getValue();
}
public update(data: Partial<DataType>) {
this._data.next({ ...this.getData(), ...data });
}
// TODO: how can we make sure to call this.
public destroy(): void {
this._data.unsubscribe();
}
}

View File

@@ -1,35 +1,46 @@
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { UmbPropertyActionMenuContext } from '../shared/property-action-menu/property-action-menu.context';
//import type { UmbPropertyActionMenuContext } from '../shared/property-action-menu/property-action-menu.context';
import { UmbPropertyAction } from '../shared/property-action/property-action.model';
import type { UmbWorkspacePropertyContext } from '../../components/workspace-property/workspace-property.context';
import { UmbLitElement } from '@umbraco-cms/element';
@customElement('umb-property-action-clear')
export class UmbPropertyActionClearElement extends UmbLitElement implements UmbPropertyAction {
@property()
value = '';
private _propertyActionMenuContext?: UmbPropertyActionMenuContext;
// THESE OUT COMMENTED CODE IS USED FOR THE EXAMPLE BELOW, TODO: Should be transferred to some documentation.
//private _propertyActionMenuContext?: UmbPropertyActionMenuContext;
private _propertyContext?: UmbWorkspacePropertyContext;
constructor() {
super();
/*
this.consumeContext('umbPropertyActionMenu', (propertyActionsContext: UmbPropertyActionMenuContext) => {
this._propertyActionMenuContext = propertyActionsContext;
});
*/
this.consumeContext('umbPropertyContext', (propertyContext: UmbWorkspacePropertyContext) => {
this._propertyContext = propertyContext;
});
}
private _handleLabelClick() {
this._clearValue();
// TODO: how do we want to close the menu? Testing an event based approach and context api approach
// this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));
this._propertyActionMenuContext?.close();
this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));
// Or you can do this:
//this._propertyActionMenuContext?.close();
}
private _clearValue() {
// TODO: how do we want to update the value? Testing an event based approach. We need to test an api based approach too.
this.value = '';
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
//this.value = '';// This is though bad as it assumes we are dealing with a string. So wouldn't work as a generalized element.
//this.dispatchEvent(new CustomEvent('property-value-change'));
// Or you can do this:
this._propertyContext?.resetValue();// This resets value to what the property wants.
}
render() {

View File

@@ -1,13 +1,22 @@
import { Observable, ReplaySubject } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
import { UmbContextProviderController } from 'src/core/context-api/provide/context-provider.controller';
import type { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin';
export class UmbPropertyActionMenuContext {
private _isOpen: ReplaySubject<boolean> = new ReplaySubject(1);
public readonly isOpen: Observable<boolean> = this._isOpen.asObservable();
private _isOpen = new BehaviorSubject(false);
public readonly isOpen = this._isOpen.asObservable();
constructor(host: UmbControllerHostInterface) {
new UmbContextProviderController(host, 'umbPropertyActionMenu', this);
}
toggle() {
this._isOpen.next(!this._isOpen.getValue());
}
open() {
this._isOpen.next(true);
}
close() {
this._isOpen.next(false);
}

View File

@@ -8,6 +8,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
import '../property-action/property-action.element';
import { UmbLitElement } from '@umbraco-cms/element';
import { UmbObserverController } from '@umbraco-cms/observable-api';
@customElement('umb-property-action-menu')
export class UmbPropertyActionMenuElement extends UmbLitElement {
@@ -40,87 +41,91 @@ export class UmbPropertyActionMenuElement extends UmbLitElement {
`,
];
@property()
public propertyEditorUIAlias = '';
// TODO: we need to investigate context api vs values props and events
@property()
public value?: string;
@property()
set propertyEditorUIAlias(alias: string) {
this._observeActions(alias);
}
private _actionsObserver?: UmbObserverController<ManifestPropertyAction[]>;
@state()
private _actions: Array<ManifestPropertyAction> = [];
@state()
private _open = false;
private _propertyActionMenuContext = new UmbPropertyActionMenuContext();
private _propertyActionMenuContext = new UmbPropertyActionMenuContext(this);
connectedCallback(): void {
super.connectedCallback();
constructor() {
super();
this._observePropertyActions();
this._observePropertyActionMenuOpenState();
this.observe(this._propertyActionMenuContext.isOpen, (value) => {
this._open = value;
});
this.provideContext('umbPropertyActionMenu', this._propertyActionMenuContext);
this.addEventListener('close', (e) => {
this._propertyActionMenuContext.close();
e.stopPropagation();
});
}
private _observePropertyActions() {
this.observe(
private _observeActions(alias: string) {
this._actionsObserver?.destroy();
this._actionsObserver = this.observe(
umbExtensionsRegistry
.extensionsOfType('propertyAction')
.pipe(
map((propertyActions) =>
propertyActions.filter((propertyAction) =>
propertyAction.meta.propertyEditors.includes(this.propertyEditorUIAlias)
map((propertyActions) => {
return propertyActions.filter((propertyAction) =>
propertyAction.meta.propertyEditors.includes(alias)
)
}
)
),
(propertyActionManifests) => {
this._actions = propertyActionManifests;
(manifests) => {
this._actions = manifests;
}
);
}
private _observePropertyActionMenuOpenState() {
this.observe(this._propertyActionMenuContext.isOpen, (value) => {
this._open = value;
});
}
private _toggleMenu() {
this._open ? this._propertyActionMenuContext.close() : this._propertyActionMenuContext.open();
this._propertyActionMenuContext.toggle();
}
private _handleClose(event: CustomEvent) {
this._open = false;
this._propertyActionMenuContext.close();
event.stopPropagation();
}
render() {
return html`
${this._actions.length > 0
? html`
<uui-popover id="popover" placement="bottom-start" .open=${this._open} @close="${this._handleClose}">
<uui-button
id="popover-trigger"
slot="trigger"
look="secondary"
label="More"
@click="${this._toggleMenu}"
compact>
<uui-symbol-more id="more-symbol"></uui-symbol-more>
</uui-button>
return (this._actions.length > 0) ?
html`
<uui-popover id="popover" placement="bottom-start" .open=${this._open} @close="${this._handleClose}">
<uui-button
id="popover-trigger"
slot="trigger"
look="secondary"
label="More"
@click="${this._toggleMenu}"
compact>
<uui-symbol-more id="more-symbol"></uui-symbol-more>
</uui-button>
<div slot="popover" id="dropdown">
${this._actions.map(
(action) => html`
<umb-property-action .propertyAction=${action} .value="${this.value}"></umb-property-action>
`
)}
</div>
</uui-popover>
`
: ''}
`;
<div slot="popover" id="dropdown">
${this._actions.map(
(action) => html`
<umb-property-action .propertyAction=${action} .value="${this.value}"></umb-property-action>
`
)}
</div>
</uui-popover>
`
: '';
}
}

View File

@@ -5,7 +5,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import type { PropertyEditorConfigDefaultData, PropertyEditorConfigProperty } from '@umbraco-cms/models';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
import '../../../components/entity-property/entity-property.element';
import '../../../components/workspace-property/workspace-property.element';
import { UmbLitElement } from '@umbraco-cms/element';
/**
@@ -104,12 +104,12 @@ export class UmbPropertyEditorConfigElement extends UmbLitElement {
? html`
${this._properties?.map(
(property) => html`
<umb-entity-property
<umb-workspace-property
label="${property.label}"
description="${ifDefined(property.description)}"
alias="${property.alias}"
property-editor-ui-alias="${property.propertyEditorUI}"
.value=${this._getValue(property)}></umb-entity-property>
.value=${this._getValue(property)}></umb-workspace-property>
`
)}
`

View File

@@ -90,7 +90,7 @@ export class UmbPropertyEditorUIContentPickerElement extends UmbLitElement {
private _setValue(newValue: Array<string>) {
this.value = newValue;
this._observePickedDocuments();
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
this.dispatchEvent(new CustomEvent('property-value-change'));
}
private _renderItem(item: FolderTreeItem) {

View File

@@ -21,7 +21,7 @@ export class UmbPropertyEditorUINumberElement extends LitElement {
private onInput(e: InputEvent) {
this.value = (e.target as HTMLInputElement).value;
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
this.dispatchEvent(new CustomEvent('property-value-change'));
}
render() {

View File

@@ -21,7 +21,7 @@ export class UmbPropertyEditorUITextBoxElement extends LitElement {
private onInput(e: InputEvent) {
this.value = (e.target as HTMLInputElement).value;
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
this.dispatchEvent(new CustomEvent('property-value-change'));
}
render() {

View File

@@ -1,8 +1,9 @@
import { css, html } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property } from 'lit/decorators.js';
import { UmbWorkspacePropertyContext } from 'src/backoffice/shared/components/entity-property/workspace-property.context';
import type { UmbWorkspacePropertyContext } from 'src/backoffice/shared/components/workspace-property/workspace-property.context';
import { UmbLitElement } from '@umbraco-cms/element';
import { UUITextareaElement } from '@umbraco-ui/uui';
@customElement('umb-property-editor-ui-textarea')
export class UmbPropertyEditorUITextareaElement extends UmbLitElement {
@@ -26,21 +27,19 @@ export class UmbPropertyEditorUITextareaElement extends UmbLitElement {
constructor() {
super();
this.consumeContext('umbPropertyContext', (instance) => {
this.consumeContext('umbPropertyContext', (instance: UmbWorkspacePropertyContext<string>) => {
this.propertyContext = instance;
});
}
private onInput(e: InputEvent) {
this.value = (e.target as HTMLInputElement).value;
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
this.value = (e.target as UUITextareaElement).value as string;
this.dispatchEvent(new CustomEvent('property-value-change'));
}
render() {
return html`
<uui-textarea .value=${this.value} @input=${this.onInput}></uui-textarea>
${this.config?.map((property: any) => html`<div>${property.alias}: ${property.value}</div>`)}
<button @click=${() => this.propertyContext?.resetValue()}>Reset</button>`;
<uui-textarea .value=${this.value} @input=${this.onInput}></uui-textarea>`;
}
}

View File

@@ -21,4 +21,9 @@ export class UmbWorkspaceUserGroupContext extends UmbWorkspaceContentContext<
constructor(host: UmbControllerHostInterface) {
super(host, DefaultDataTypeData, 'umbUserStore', 'userGroup');
}
public setPropertyValue(alias: string, value: unknown) {
throw new Error("setPropertyValue is not implemented for UmbWorkspaceUserGroupContext")
}
}

View File

@@ -24,4 +24,8 @@ export class UmbWorkspaceUserContext extends UmbWorkspaceContentContext<UmbUserS
constructor(host: UmbControllerHostInterface) {
super(host, DefaultDataTypeData, 'umbUserStore', 'user');
}
public setPropertyValue(alias: string, value: unknown) {
throw new Error("setPropertyValue is not implemented for UmbWorkspaceUserContext")
}
}

View File

@@ -32,7 +32,7 @@ export const data: Array<DataTypeDetails> = [
parentKey: null,
isFolder: false,
propertyEditorModelAlias: 'Umbraco.TextArea',
propertyEditorUIAlias: 'Umb.PropertyEditorUI.Textarea',
propertyEditorUIAlias: 'Umb.PropertyEditorUI.TextArea',
data: [
{
alias: 'maxChars',

View File

@@ -24,6 +24,13 @@ export interface Entity {
parentKey: string | null;
}
export interface ContentDetails extends ContentTreeItem {
isTrashed: boolean; // TODO: remove only temp part of refactor
properties: Array<ContentProperty>;
//data: Array<ContentPropertyData>;
//layout?: any; // TODO: define layout type - make it non-optional
}
export interface UserEntity extends Entity {
type: 'user';
}

View File

@@ -0,0 +1,88 @@
import { BehaviorSubject, distinctUntilChanged, map, Observable, shareReplay } from "rxjs";
function deepFreeze<T>(inObj: T): T {
Object.freeze(inObj);
Object.getOwnPropertyNames(inObj).forEach(function (prop) {
// eslint-disable-next-line no-prototype-builtins
if ((inObj as any).hasOwnProperty(prop)
&& (inObj as any)[prop] != null
&& typeof (inObj as any)[prop] === 'object'
&& !Object.isFrozen((inObj as any)[prop])) {
deepFreeze((inObj as any)[prop]);
}
});
return inObj;
}
export function naiveObjectComparison(objOne: any, objTwo: any): boolean {
return JSON.stringify(objOne) === JSON.stringify(objTwo);
}
type MappingFunction<T, R> = (mappable: T) => R;
type MemoizationFunction<R> = (previousResult: R, currentResult: R) => boolean;
function defaultMemoization(previousValue: any, currentValue: any): boolean {
if (typeof previousValue === 'object' && typeof currentValue === 'object') {
return naiveObjectComparison(previousValue, currentValue);
}
return previousValue === currentValue;
}
/**
* @export
* @method CreateObservablePart
* @param {Observable<T>} source - RxJS Subject to use for this Observable.
* @param {(mappable: T) => R} mappingFunction - Method to return the part for this Observable to return.
* @param {(previousResult: R, currentResult: R) => boolean} [memoizationFunction] - Method to Compare if the data has changed. Should return true when data is different.
* @description - Creates a RxJS Observable from RxJS Subject.
* @example <caption>Example create a Observable for part of the data Subject.</caption>
* public readonly myPart = CreateObservablePart(this._data, (data) => data.myPart);
*/
export function CreateObservablePart<T, R> (
source$: Observable<T>,
mappingFunction: MappingFunction<T, R>,
memoizationFunction?: MemoizationFunction<R>
): Observable<R> {
return source$.pipe(
map(mappingFunction),
distinctUntilChanged(memoizationFunction || defaultMemoization),
shareReplay(1)
)
}
/**
* @export
* @class UniqueBehaviorSubject
* @extends {BehaviorSubject<T>}
* @description - A RxJS BehaviorSubject which deepFreezes the data to ensure its not manipulated from any implementations.
* Additionally the Subject ensures the data is unique, not updating any Observes unless there is an actual change of the content.
*/
export class UniqueBehaviorSubject<T> extends BehaviorSubject<T> {
constructor(initialData: T) {
super(deepFreeze(initialData));
}
next(newData: T): void {
const frozenData = deepFreeze(newData);
// Only update data if its different than current data.
if (!naiveObjectComparison(frozenData, this.getValue())) {
super.next(frozenData);
}
}
/**
* Partial update data set, only works for Objects.
* TODO: consider moving this into a specific class for Objects?
* Consider doing similar for Array?
*/
update(data: Partial<T>) {
this.next({ ...this.getValue(), ...data });
}
}