From d43abcc8c94dcf5bef6d2b82f86e5796e5a588f5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 18 Apr 2021 16:57:27 +0200 Subject: [PATCH] Feature/settings (#686) * First settings. * Migrations. * Angular update. * Type safe templates * Type safe templates * Backend Tests and minor fixes. * Some tests. * Cleanup and tests. * Fix tests for title component. * Remove patterns test. * Tests for settings. --- .../Actions/Webhook/WebhookActionHandler.cs | 8 +- backend/i18n/frontend_en.json | 16 +- backend/i18n/frontend_it.json | 16 +- backend/i18n/frontend_nl.json | 16 +- backend/i18n/source/backend_en.json | 6 +- backend/i18n/source/backend_it.json | 5 - backend/i18n/source/backend_nl.json | 5 - backend/i18n/source/frontend_en.json | 16 +- backend/i18n/source/frontend_it.json | 6 - backend/i18n/source/frontend_nl.json | 6 - backend/src/Migrations/MigrationPath.cs | 8 +- .../Migrations/CreateAppSettings.cs | 112 + .../OldEvents}/AppPatternAdded.cs | 5 +- .../OldEvents}/AppPatternDeleted.cs | 5 +- .../OldEvents}/AppPatternUpdated.cs | 5 +- .../Apps/AppPatterns.cs | 71 - .../Apps/AppSettings.cs | 28 + .../Apps/Editor.cs | 15 + .../Apps/Json/AppPatternsSurrogate.cs | 33 - .../Apps/{AppPattern.cs => Pattern.cs} | 6 +- .../Apps/Role.cs | 26 +- .../Apps/Roles.cs | 1 - .../Contents/Workflow.cs | 12 +- .../Apps/AppHistoryEventsCreator.cs | 28 +- .../Apps/AppSettingsSearchSource.cs | 3 - .../Apps/Commands/AddPattern.cs | 27 - ...{UpdatePattern.cs => UpdateAppSettings.cs} | 14 +- .../DomainObject/AppDomainObject.State.cs | 52 +- .../Apps/DomainObject/AppDomainObject.cs | 81 +- .../Apps/DomainObject/Guards/GuardApp.cs | 66 + .../DomainObject/Guards/GuardAppPatterns.cs | 109 - .../Apps/IAppEntity.cs | 2 +- .../Apps/InitialPatterns.cs | 25 - .../DeletePattern.cs => InitialSettings.cs} | 8 +- .../Apps/RolePermissionsProvider.cs | 32 +- .../Squidex.Domain.Apps.Entities/Context.cs | 3 +- .../Apps/AppPlanChanged.cs | 2 +- .../Apps/AppSettingsUpdated.cs | 18 + .../MongoDb/MongoExtensions.cs | 12 +- .../Squidex.Shared/PermissionExtensions.cs | 36 + backend/src/Squidex.Shared/Permissions.cs | 101 +- backend/src/Squidex.Shared/Texts.it.resx | 18 +- backend/src/Squidex.Shared/Texts.nl.resx | 18 +- backend/src/Squidex.Shared/Texts.resx | 18 +- backend/src/Squidex.Web/Resources.cs | 109 +- .../Controllers/Apps/AppPatternsController.cs | 151 - .../Api/Controllers/Apps/AppsController.cs | 84 +- .../Api/Controllers/Apps/Models/AppDto.cs | 47 +- .../Controllers/Apps/Models/AppSettingsDto.cs | 74 + .../Api/Controllers/Apps/Models/EditorDto.cs | 26 + .../Api/Controllers/Apps/Models/PatternDto.cs | 31 +- .../Controllers/Apps/Models/PatternsDto.cs | 47 - .../Apps/Models/UpdateAppSettingsDto.cs | 54 + .../Apps/Models/UpdatePatternDto.cs | 44 - .../News/Service/FeaturesService.cs | 2 + .../Areas/Api/Controllers/UI/MyUIOptions.cs | 1 + .../src/Squidex/Config/Domain/AppsServices.cs | 15 +- .../Config/Domain/MigrationServices.cs | 3 + .../Config/Domain/SerializationServices.cs | 1 - backend/src/Squidex/appsettings.json | 4 - .../Model/Apps/AppPatternJsonTests.cs | 39 - .../Model/Apps/AppPatternsTests.cs | 86 - .../Model/Apps/RolesTests.cs | 8 +- .../TestHelpers/TestUtils.cs | 1 - .../Apps/AppSettingsSearchSourceTests.cs | 17 - .../Apps/DomainObject/AppDomainObjectTests.cs | 112 +- .../Guards/GuardAppPatternsTests.cs | 192 - .../Apps/DomainObject/Guards/GuardAppTests.cs | 121 + .../DefaultWorkflowsValidatorTests.cs | 10 +- .../TestSuite/TestSuite.ApiTests/AppTests.cs | 72 +- .../TestSuite.ApiTests.csproj | 2 +- .../TestSuite.LoadTests.csproj | 2 +- .../TestSuite.Shared/TestSuite.Shared.csproj | 4 +- frontend/app-config/webpack.config.js | 7 +- frontend/app/app.component.ts | 2 +- .../pages/cluster/cluster-page.component.html | 2 +- .../event-consumer.component.html | 2 +- .../event-consumers-page.component.html | 4 +- .../pages/users/user-page.component.html | 2 +- .../pages/users/user-page.component.ts | 2 +- .../pages/users/users-page.component.html | 4 +- .../services/event-consumers.service.spec.ts | 14 +- .../services/users.service.spec.ts | 18 +- .../administration/services/users.service.ts | 18 +- .../administration/state/users.state.ts | 2 +- .../pages/graphql/graphql-page.component.html | 2 +- .../apps/pages/onboarding-dialog.component.ts | 2 +- .../pages/assets-filters-page.component.html | 6 +- .../assets/pages/assets-page.component.html | 6 +- .../comments/comments-page.component.html | 2 +- .../content-history-page.component.html | 16 +- .../content/content-history-page.component.ts | 4 + .../pages/content/content-page.component.html | 6 +- .../pages/content/content-page.component.ts | 2 +- .../editor/content-editor.component.ts | 2 +- .../editor/content-field.component.html | 2 +- .../content/editor/content-field.component.ts | 4 +- .../editor/content-section.component.html | 8 +- .../editor/content-section.component.ts | 4 +- .../editor/field-languages.component.html | 2 +- .../editor/field-languages.component.ts | 2 +- .../content-references.component.html | 4 +- .../contents-filters-page.component.html | 2 +- .../contents/contents-page.component.html | 14 +- .../pages/contents/contents-page.component.ts | 4 + .../contents/custom-view-editor.component.ts | 4 +- .../pages/schemas/schemas-page.component.html | 2 +- .../pages/sidebar/sidebar-page.component.html | 4 +- .../shared/content-extension.component.ts | 24 +- .../shared/content-status.component.ts | 6 +- .../shared/due-time-selector.component.html | 2 +- .../shared/due-time-selector.component.ts | 12 +- .../shared/forms/array-editor.component.ts | 2 +- .../shared/forms/array-item.component.html | 2 +- .../shared/forms/array-item.component.ts | 8 +- .../shared/forms/array-section.component.html | 2 +- .../shared/forms/array-section.component.ts | 2 +- .../shared/forms/assets-editor.component.html | 4 +- .../shared/forms/field-editor.component.html | 10 +- .../shared/forms/field-editor.component.ts | 8 +- .../shared/forms/iframe-editor.component.ts | 24 +- .../forms/stock-photo-editor.component.html | 4 +- .../list/content-list-field.component.html | 18 +- .../list/content-list-field.component.ts | 4 +- .../shared/list/content.component.html | 2 +- .../content/shared/list/content.component.ts | 4 +- .../references/content-creator.component.html | 4 +- .../references/content-creator.component.ts | 4 +- .../content-selector-item.component.ts | 4 +- .../content-selector.component.html | 6 +- .../references/content-selector.component.ts | 2 +- .../references/reference-item.component.ts | 8 +- .../references/references-editor.component.ts | 2 +- .../cards/api-performance-card.component.ts | 2 +- .../pages/cards/api-traffic-card.component.ts | 2 +- .../pages/dashboard-config.component.html | 2 +- .../pages/dashboard-config.component.ts | 4 +- .../pages/dashboard-page.component.html | 4 +- .../pages/dashboard-page.component.scss | 2 +- .../events/rule-events-page.component.html | 2 +- .../pages/rules/rule-element.component.ts | 4 +- .../pages/rules/rule-wizard.component.html | 2 +- .../pages/rules/rule-wizard.component.ts | 12 +- .../pages/rules/rules-page.component.html | 8 +- .../common/schema-edit-form.component.ts | 2 +- .../schema/fields/field-wizard.component.html | 8 +- .../schema/fields/field-wizard.component.ts | 11 +- .../pages/schema/fields/field.component.html | 13 +- .../pages/schema/fields/field.component.ts | 6 +- .../field-form-validation.component.html | 2 +- .../forms/field-form-validation.component.ts | 6 +- .../fields/forms/field-form.component.html | 5 +- .../fields/forms/field-form.component.ts | 10 +- .../fields/schema-fields.component.html | 18 +- .../schema/fields/schema-fields.component.ts | 8 +- .../types/assets-validation.component.ts | 2 +- .../types/boolean-validation.component.ts | 2 +- .../types/date-time-validation.component.html | 6 +- .../types/date-time-validation.component.ts | 2 +- .../types/number-validation.component.ts | 2 +- .../references-validation.component.html | 2 +- .../types/references-validation.component.ts | 2 +- .../types/string-validation.component.html | 13 +- .../types/string-validation.component.scss | 2 +- .../types/string-validation.component.ts | 10 +- .../fields/types/tags-validation.component.ts | 2 +- .../schema-preview-urls-form.component.html | 8 +- .../schema-field-rules-form.component.html | 8 +- .../pages/schema/schema-page.component.html | 6 +- .../pages/schemas/schema-form.component.html | 2 +- .../pages/schemas/schemas-page.component.html | 2 +- .../app/features/settings/declarations.ts | 3 +- frontend/app/features/settings/module.ts | 26 +- .../pages/backups/backups-page.component.html | 2 +- .../pages/clients/clients-page.component.html | 2 +- .../contributors/contributor.component.ts | 2 +- .../contributors-page.component.html | 4 +- .../pages/languages/language.component.html | 7 +- .../pages/languages/language.component.ts | 4 +- .../languages/languages-page.component.html | 5 +- .../languages/languages-page.component.ts | 4 +- .../pages/more/more-page.component.ts | 4 +- .../pages/patterns/pattern.component.html | 46 - .../pages/patterns/pattern.component.scss | 8 - .../pages/patterns/pattern.component.ts | 76 - .../patterns/patterns-page.component.html | 46 - .../patterns/patterns-page.component.scss | 0 .../pages/patterns/patterns-page.component.ts | 33 - .../settings/pages/plans/plan.component.html | 6 +- .../pages/plans/plans-page.component.html | 2 +- .../pages/roles/roles-page.component.html | 4 +- .../settings/settings-page.component.html | 149 + .../settings/settings-page.component.scss | 6 + .../pages/settings/settings-page.component.ts | 67 + .../workflows/workflow-step.component.html | 8 +- .../workflows/workflow-step.component.ts | 6 +- .../workflow-transition.component.html | 4 +- .../workflow-transition.component.ts | 2 +- .../pages/workflows/workflow.component.html | 10 +- .../workflows/workflows-page.component.html | 2 +- .../settings/settings-area.component.html | 6 +- .../app/framework/angular/avatar.component.ts | 4 +- .../angular/forms/confirm-click.directive.ts | 6 +- .../angular/forms/editable-title.component.ts | 2 +- .../forms/editors/autocomplete.component.ts | 2 +- .../editors/checkbox-group.component.html | 2 +- .../forms/editors/code-editor.component.ts | 4 +- .../forms/editors/color-picker.component.ts | 4 +- .../editors/date-time-editor.component.ts | 10 +- .../forms/editors/dropdown.component.ts | 8 +- .../editors/localized-input.component.ts | 2 +- .../forms/editors/tag-editor.component.ts | 24 +- .../angular/forms/editors/toggle.component.ts | 6 +- .../angular/forms/focus-on-init.directive.ts | 4 +- .../angular/forms/form-alert.component.ts | 6 +- .../angular/forms/form-error.component.ts | 4 +- .../angular/forms/form-hint.component.ts | 4 +- .../framework/angular/forms/forms-helper.ts | 2 +- .../forms/indeterminate-value.directive.ts | 4 +- frontend/app/framework/angular/forms/model.ts | 2 +- .../angular/forms/progress-bar.component.ts | 4 +- .../forms/undefinable-form-array.spec.ts | 6 +- .../angular/http/caching.interceptor.ts | 2 +- .../angular/language-selector.component.html | 4 +- .../angular/language-selector.component.ts | 2 +- .../angular/list-view.component.scss | 2 +- .../framework/angular/list-view.component.ts | 12 +- .../modals/dialog-renderer.component.html | 2 +- .../angular/modals/modal-dialog.component.ts | 10 +- .../modals/modal-placement.directive.ts | 4 +- .../framework/angular/pager.component.html | 2 +- .../app/framework/angular/pager.component.ts | 18 +- .../app/framework/angular/panel.component.ts | 20 +- .../framework/angular/pipes/highlight.pipe.ts | 2 +- .../framework/angular/pipes/markdown.pipe.ts | 4 +- .../angular/routers/parent-link.directive.ts | 2 +- .../framework/angular/shortcut.component.ts | 2 +- .../framework/angular/stateful.component.ts | 8 +- .../framework/angular/stop-click.directive.ts | 2 +- .../framework/angular/title.component.spec.ts | 48 + .../framework/services/analytics.service.ts | 4 - .../services/clipboard.service.spec.ts | 8 +- .../framework/services/clipboard.service.ts | 4 - .../framework/services/dialog.service.spec.ts | 19 +- .../app/framework/services/dialog.service.ts | 4 - .../services/loading.service.spec.ts | 8 +- .../app/framework/services/loading.service.ts | 4 - .../services/local-store.service.spec.ts | 12 +- .../framework/services/local-store.service.ts | 4 - .../services/localizer.service.spec.ts | 8 +- .../framework/services/localizer.service.ts | 4 - .../services/message-bus.service.spec.ts | 8 +- .../framework/services/message-bus.service.ts | 4 - .../services/onboarding.service.spec.ts | 8 +- .../framework/services/onboarding.service.ts | 4 - .../app/framework/services/resize.service.ts | 4 - .../services/resource-loader.service.ts | 6 +- .../services/shortcut.service.spec.ts | 8 +- .../framework/services/shortcut.service.ts | 4 - .../framework/services/temp.service.spec.ts | 8 +- .../app/framework/services/temp.service.ts | 4 - .../framework/services/title.service.spec.ts | 8 +- .../app/framework/services/title.service.ts | 4 - .../app/framework/utils/interpolator.spec.ts | 6 + frontend/app/framework/utils/interpolator.ts | 12 +- frontend/app/framework/utils/string-helper.ts | 8 +- .../app/framework/utils/tag-values.spec.ts | 213 + frontend/app/framework/utils/tag-values.ts | 8 +- frontend/app/framework/utils/types.spec.ts | 2 +- frontend/app/framework/utils/version.ts | 2 +- .../assets/asset-dialog.component.html | 14 +- .../assets/asset-folder.component.ts | 14 +- .../components/assets/asset-path.component.ts | 4 +- .../components/assets/asset.component.scss | 2 +- .../components/assets/asset.component.ts | 14 +- .../assets/assets-list.component.html | 12 +- .../assets/assets-list.component.ts | 14 +- .../assets/assets-selector.component.html | 10 +- .../comments/comment.component.html | 6 +- .../components/comments/comment.component.ts | 8 +- .../comments/comments.component.html | 2 +- .../forms/geolocation-editor.component.ts | 10 +- .../forms/markdown-editor.component.ts | 8 +- .../forms/rich-editor.component.html | 2 +- .../components/help/help-markdown.pipe.ts | 2 +- .../components/help/help.component.html | 2 +- .../history/history-list.component.ts | 2 +- .../components/history/history.component.html | 2 +- .../shared/components/notifo.component.html | 2 +- .../components/schema-category.component.html | 2 +- .../components/schema-category.component.ts | 2 +- .../queries/filter-logical.component.ts | 12 +- .../search/queries/query-path.component.html | 2 +- .../search/queries/query.component.html | 2 +- .../search/queries/query.component.ts | 2 +- .../search/query-list.component.html | 2 +- .../components/search/query-list.component.ts | 6 +- .../search/search-form.component.html | 6 +- .../search/search-form.component.ts | 12 +- .../search/shared-queries.component.ts | 2 +- .../components/table-header.component.ts | 2 +- .../interceptors/auth.interceptor.spec.ts | 4 +- frontend/app/shared/internal.ts | 4 +- frontend/app/shared/module.ts | 4 +- .../shared/services/app-languages.service.ts | 25 +- .../app/shared/services/apps.service.spec.ts | 113 +- frontend/app/shared/services/apps.service.ts | 96 +- .../shared/services/assets.service.spec.ts | 44 +- .../app/shared/services/assets.service.ts | 64 +- frontend/app/shared/services/auth.service.ts | 45 +- .../app/shared/services/backups.service.ts | 6 +- .../app/shared/services/clients.service.ts | 27 +- .../app/shared/services/comments.service.ts | 6 +- .../shared/services/contents.service.spec.ts | 14 +- .../app/shared/services/contents.service.ts | 52 +- .../shared/services/contributors.service.ts | 22 +- .../shared/services/patterns.service.spec.ts | 174 - .../app/shared/services/patterns.service.ts | 125 - frontend/app/shared/services/plans.service.ts | 20 +- frontend/app/shared/services/roles.service.ts | 26 +- .../app/shared/services/rules.service.spec.ts | 46 +- frontend/app/shared/services/rules.service.ts | 23 +- .../shared/services/schemas.service.spec.ts | 68 +- .../app/shared/services/schemas.service.ts | 92 +- frontend/app/shared/services/schemas.types.ts | 6 +- .../shared/services/translations.service.ts | 7 +- frontend/app/shared/services/ui.service.ts | 5 +- .../app/shared/services/workflows.service.ts | 37 +- frontend/app/shared/state/apps.forms.ts | 98 +- frontend/app/shared/state/apps.state.spec.ts | 41 +- frontend/app/shared/state/apps.state.ts | 60 +- frontend/app/shared/state/assets.forms.ts | 3 + .../app/shared/state/assets.state.spec.ts | 2 +- frontend/app/shared/state/assets.state.ts | 2 +- .../shared/state/contents.forms-helpers.ts | 7 +- frontend/app/shared/state/contents.state.ts | 4 +- frontend/app/shared/state/languages.state.ts | 2 +- frontend/app/shared/state/patterns.forms.ts | 34 - .../app/shared/state/patterns.state.spec.ts | 126 - frontend/app/shared/state/patterns.state.ts | 123 - frontend/app/shared/state/plans.state.ts | 20 +- frontend/app/shared/state/query.ts | 10 +- frontend/app/shared/state/schemas.forms.ts | 8 + .../app/shared/state/schemas.state.spec.ts | 34 +- .../app/shared/state/table-fields.spec.ts | 4 +- frontend/app/shared/state/table-fields.ts | 2 +- frontend/app/shared/state/ui.state.ts | 36 +- frontend/app/shared/utils/array-extensions.ts | 1 - .../app/shared/utils/editor-utils.spec.ts | 62 + frontend/app/shared/utils/editor-utils.ts | 25 + .../pages/internal/apps-menu.component.scss | 2 +- .../shell/pages/internal/logo.component.ts | 2 +- frontend/package-lock.json | 5966 +++++++++-------- frontend/package.json | 113 +- frontend/tsconfig.json | 4 +- 355 files changed, 6012 insertions(+), 6328 deletions(-) create mode 100644 backend/src/Migrations/Migrations/CreateAppSettings.cs rename backend/src/{Squidex.Domain.Apps.Events/Apps => Migrations/OldEvents}/AppPatternAdded.cs (86%) rename backend/src/{Squidex.Domain.Apps.Events/Apps => Migrations/OldEvents}/AppPatternDeleted.cs (83%) rename backend/src/{Squidex.Domain.Apps.Events/Apps => Migrations/OldEvents}/AppPatternUpdated.cs (86%) delete mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppSettings.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Apps/Editor.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsSurrogate.cs rename backend/src/Squidex.Domain.Apps.Core.Model/Apps/{AppPattern.cs => Pattern.cs} (81%) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs rename backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/{UpdatePattern.cs => UpdateAppSettings.cs} (55%) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppPatterns.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs rename backend/src/Squidex.Domain.Apps.Entities/Apps/{Commands/DeletePattern.cs => InitialSettings.cs} (67%) create mode 100644 backend/src/Squidex.Domain.Apps.Events/Apps/AppSettingsUpdated.cs create mode 100644 backend/src/Squidex.Shared/PermissionExtensions.cs delete mode 100644 backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs create mode 100644 backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs create mode 100644 backend/src/Squidex/Areas/Api/Controllers/Apps/Models/EditorDto.cs delete mode 100644 backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs create mode 100644 backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateAppSettingsDto.cs delete mode 100644 backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs delete mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs delete mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs delete mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppPatternsTests.cs delete mode 100644 frontend/app/features/settings/pages/patterns/pattern.component.html delete mode 100644 frontend/app/features/settings/pages/patterns/pattern.component.scss delete mode 100644 frontend/app/features/settings/pages/patterns/pattern.component.ts delete mode 100644 frontend/app/features/settings/pages/patterns/patterns-page.component.html delete mode 100644 frontend/app/features/settings/pages/patterns/patterns-page.component.scss delete mode 100644 frontend/app/features/settings/pages/patterns/patterns-page.component.ts create mode 100644 frontend/app/features/settings/pages/settings/settings-page.component.html create mode 100644 frontend/app/features/settings/pages/settings/settings-page.component.scss create mode 100644 frontend/app/features/settings/pages/settings/settings-page.component.ts create mode 100644 frontend/app/framework/angular/title.component.spec.ts create mode 100644 frontend/app/framework/utils/tag-values.spec.ts delete mode 100644 frontend/app/shared/services/patterns.service.spec.ts delete mode 100644 frontend/app/shared/services/patterns.service.ts delete mode 100644 frontend/app/shared/state/patterns.forms.ts delete mode 100644 frontend/app/shared/state/patterns.state.spec.ts delete mode 100644 frontend/app/shared/state/patterns.state.ts create mode 100644 frontend/app/shared/utils/editor-utils.spec.ts create mode 100644 frontend/app/shared/utils/editor-utils.ts diff --git a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs index 76baa1f58..659529ed4 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs @@ -79,12 +79,12 @@ namespace Squidex.Extensions.Actions.Webhook if (indexEqual > 0 && indexEqual < line.Length - 1) { - var key = line.Substring(0, indexEqual); - var val = line[(indexEqual + 1)..]; + var headerKey = line.Substring(0, indexEqual); + var headerValue = line[(indexEqual + 1)..]; - val = await FormatAsync(val, @event); + headerValue = await FormatAsync(headerValue, @event); - headersDictionary[key] = val; + headersDictionary[headerKey] = headerValue; } } diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index 5fb90b0f2..ea09a2bcb 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -52,6 +52,11 @@ "apps.uploadImageTooBig": "App image is too big.", "apps.welcomeSubtitle": "Welcome to Squidex.", "apps.welcomeTitle": "Hi {user}", + "appSettings.hideScheduler": "Hide dialog for scheduled publishing", + "appSettings.refreshTooltip": "Refresh UI Settings (CTRL + SHIFT + R)", + "appSettings.reloaded": "UI Settings reloaded.", + "appSettings.title": "UI Settings", + "appSettings.updateFailed": "Failed to update UI settings. Please reload.", "assets.createFolder": "Create Folder", "assets.createFolderFailed": "Failed to create asset folder. Please reload.", "assets.createFolderTooltip": "Create new folder (CTRL + SHIFT + G)", @@ -520,6 +525,10 @@ "dashboard.trafficSummaryCard": "API Traffic Summary", "dashboard.welcomeText": "Welcome to **{app}** dashboard.", "dashboard.welcomeTitle": "Hi {user}", + "editors.deleteConfirmText": "Do you really want to remove this Editor URL?", + "editors.deleteConfirmTitle": "Delete Editor URL", + "editors.empty": "No Editor URL created yet.", + "editors.title": "Custom Editors", "eventConsumers.count": "Count", "eventConsumers.loadFailed": "Failed to load event consumers. Please reload.", "eventConsumers.pageTitle": "Event Consumers", @@ -553,13 +562,8 @@ "notifo.subscripeTooltip": "Click this button to subscribe to all changes and to receive push notifications.", "patterns.deleteConfirmText": "Do you really want to remove this pattern?", "patterns.deleteConfirmTitle": "Delete pattern", - "patterns.deleteFailed": "Failed to remove pattern. Please reload.", "patterns.empty": "No pattern created yet.", - "patterns.loadFailed": "Failed to add pattern. Please reload.", - "patterns.nameValidationMessage": "Name can only contain letters, numbers, dashes and spaces.", - "patterns.refreshTooltip": "Refresh patterns (CTRL + SHIFT + R)", - "patterns.reloaded": "Patterns reloaded.", - "patterns.updateFailed": "Failed to update pattern. Please reload.", + "patterns.title": "Patterns", "plans.billingPortal": "Billing Portal", "plans.billingPortalHint": "Go to Billing Portal for payment history and subscription overview.", "plans.change": "Change", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index e63be4ab1..a69449f86 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -52,6 +52,11 @@ "apps.uploadImageTooBig": "L'immagine dell'app è troppo grande.", "apps.welcomeSubtitle": "Benvenuto su Squidex.", "apps.welcomeTitle": "Ciao {user}", + "appSettings.hideScheduler": "Hide dialog for scheduled publishing", + "appSettings.refreshTooltip": "Refresh UI Settings (CTRL + SHIFT + R)", + "appSettings.reloaded": "UI Settings reloaded.", + "appSettings.title": "UI Settings", + "appSettings.updateFailed": "Failed to update UI settings. Please reload.", "assets.createFolder": "Crea cartella", "assets.createFolderFailed": "Non è stato possibile creare la cartella degli asset. Per favore ricarica.", "assets.createFolderTooltip": "Crea una nuova cartella (CTRL + SHIFT + G)", @@ -520,6 +525,10 @@ "dashboard.trafficSummaryCard": "Riepilogo del traffico delle API", "dashboard.welcomeText": "Benvenuto sulla dashboard **{app}**.", "dashboard.welcomeTitle": "Ciao {user}", + "editors.deleteConfirmText": "Do you really want to remove this Editor URL?", + "editors.deleteConfirmTitle": "Delete Editor URL", + "editors.empty": "No Editor URL created yet.", + "editors.title": "Custom Editors", "eventConsumers.count": "Conteggio", "eventConsumers.loadFailed": "Non è stato possibile caricare event consumers. Per favore ricarica.", "eventConsumers.pageTitle": "Eventi degli utenti", @@ -553,13 +562,8 @@ "notifo.subscripeTooltip": "Fai clic su questo pulsante per iscriverti a tutte le modifiche e ricevere le notifiche push.", "patterns.deleteConfirmText": "Sei sicuro di voler rimuovere il pattern?", "patterns.deleteConfirmTitle": "Cancella il pattern", - "patterns.deleteFailed": "Non è stato possibile rimuovere il pattern. Per favore ricarica.", "patterns.empty": "Nessun pattern è stato ancora creato.", - "patterns.loadFailed": "Non è stato possibile aggiungere il pattern. Per favore ricarica.", - "patterns.nameValidationMessage": "Il nome può contenere solo lettere, numeri, trattini e spazi.", - "patterns.refreshTooltip": "Aggiorna i pattern (CTRL + SHIFT + R)", - "patterns.reloaded": "Pattern ricaricati.", - "patterns.updateFailed": "Non è stato possibile aggiornare pattern. Per favore ricarica.", + "patterns.title": "Patterns", "plans.billingPortal": "Portale di fatturazione", "plans.billingPortalHint": "Vai al portale di fatturazione per lo storico dei pagamenti e una panoramica per l'abbonamento.", "plans.change": "Cambia", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index 0ba628cec..b6f81061c 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -52,6 +52,11 @@ "apps.uploadImageTooBig": "App-afbeelding is te groot.", "apps.welcomeSubtitle": "Welkom bij Squidex.", "apps.welcomeTitle": "Hallo {user}", + "appSettings.hideScheduler": "Hide dialog for scheduled publishing", + "appSettings.refreshTooltip": "Refresh UI Settings (CTRL + SHIFT + R)", + "appSettings.reloaded": "UI Settings reloaded.", + "appSettings.title": "UI Settings", + "appSettings.updateFailed": "Failed to update UI settings. Please reload.", "assets.createFolder": "Map maken", "assets.createFolderFailed": "Maken van een map is mislukt. Laad opnieuw.", "assets.createFolderTooltip": "Nieuwe map maken (CTRL + SHIFT + G)", @@ -520,6 +525,10 @@ "dashboard.trafficSummaryCard": "API Verkeer Samenvatting", "dashboard.welcomeText": "Welkom bij **{app}** dashboard.", "dashboard.welcomeTitle": "Hallo {user}", + "editors.deleteConfirmText": "Do you really want to remove this Editor URL?", + "editors.deleteConfirmTitle": "Delete Editor URL", + "editors.empty": "No Editor URL created yet.", + "editors.title": "Custom Editors", "eventConsumers.count": "Tellen", "eventConsumers.loadFailed": "Kan gebeurtenisgebruikers niet laden. Laad opnieuw.", "eventConsumers.pageTitle": "Evenementconsumenten", @@ -553,13 +562,8 @@ "notifo.subscripeTooltip": "Klik op deze knop om je te abonneren op alle wijzigingen en om pushmeldingen te ontvangen.", "patterns.deleteConfirmText": "Wil je dit patroon echt verwijderen?", "patterns.deleteConfirmTitle": "Verwijder patroon", - "patterns.deleteFailed": "Verwijderen van patroon is mislukt. Laad opnieuw.", "patterns.empty": "Nog geen patroon gemaakt.", - "patterns.loadFailed": "Toevoegen van patroon is mislukt. Laad opnieuw.", - "patterns.nameValidationMessage": "Naam mag alleen letters, cijfers, streepjes en spaties bevatten.", - "patterns.refreshTooltip": "Ververs patronen (CTRL + SHIFT + R)", - "patterns.reloaded": "Patronen herladen.", - "patterns.updateFailed": "Bijwerken van patroon is mislukt. Laad opnieuw.", + "patterns.title": "Patterns", "plans.billingPortal": "Factureringsportal", "plans.billingPortalHint": "Ga naar het factureringsportaal voor betalingsgeschiedenis en abonnementsoverzicht.", "plans.change": "Wijzigen", diff --git a/backend/i18n/source/backend_en.json b/backend/i18n/source/backend_en.json index e1f0943a1..3a3d04d0d 100644 --- a/backend/i18n/source/backend_en.json +++ b/backend/i18n/source/backend_en.json @@ -18,8 +18,6 @@ "apps.languages.masterLanguageNotRemovable": "Master language cannot be removed.", "apps.nameAlreadyExists": "An app with the same name already exists.", "apps.notImage": "File is not an image", - "apps.patterns.nameAlreadyExists": "A pattern with the same name already exists.", - "apps.patterns.patternAlreadyExists": "This pattern already exists but with another name.", "apps.plans.notFound": "A plan with this id does not exist.", "apps.plans.notPlanOwner": "Plan can only changed from the user who configured the plan initially.", "apps.roles.defaultRoleNotRemovable": "Cannot delete a default role.", @@ -212,14 +210,12 @@ "history.apps.languagedRemoved": "removed language {[Language]}", "history.apps.languagedSetToMaster": "changed master language to {[Language]}", "history.apps.languagedUpdated": "updated language {[Language]}", - "history.apps.patternAdded": "added pattern {[Name]}", - "history.apps.patternDeleted": "deleted pattern {[PatternId]}", - "history.apps.patternUpdated": "updated pattern {[Name]}", "history.apps.planChanged": "changed plan to {[Plan]}", "history.apps.planReset": "resetted plan", "history.apps.roleAdded": "added role {[Name]}", "history.apps.roleDeleted": "deleted role {[Name]}", "history.apps.roleUpdated": "updated role {[Name]}", + "history.apps.settingsUpdated": "updated UI settings", "history.assets.replaced": "replaced asset.", "history.assets.updated": "updated asset.", "history.assets.uploaded": "uploaded asset.", diff --git a/backend/i18n/source/backend_it.json b/backend/i18n/source/backend_it.json index 0927d6b3f..79cf41986 100644 --- a/backend/i18n/source/backend_it.json +++ b/backend/i18n/source/backend_it.json @@ -18,8 +18,6 @@ "apps.languages.masterLanguageNotRemovable": "La lingua master non può essere rimossa.", "apps.nameAlreadyExists": "Esiste già un'app con lo stesso nome.", "apps.notImage": "Il file non è una immagine", - "apps.patterns.nameAlreadyExists": "Esiste già un pattern con lo stesso nome.", - "apps.patterns.patternAlreadyExists": "Questo pattern esiste già con un altro nome.", "apps.plans.notFound": "Non esiste un piano con questo id.", "apps.plans.notPlanOwner": "Solo l'utente che ha configurato il piano inizialmente può modificarlo.", "apps.roles.defaultRoleNotRemovable": "Non è possibile cancellare un ruolo predefinito.", @@ -213,9 +211,6 @@ "history.apps.languagedRemoved": "rimossa lingua {[Language]}", "history.apps.languagedSetToMaster": "cambiata la lingua master in {[Language]}", "history.apps.languagedUpdated": "aggiornata la lingua {[Language]}", - "history.apps.patternAdded": "ha aggiunto pattern {[Name]}", - "history.apps.patternDeleted": "ha eliminato pattern {[PatternId]}", - "history.apps.patternUpdated": "ha modificato pattern {[Name]}", "history.apps.planChanged": "ha cambiato il piano in {[Plan]}", "history.apps.planReset": "ha riconfigurato il piano", "history.apps.roleAdded": "ha aggiunto il ruolo {[Name]}", diff --git a/backend/i18n/source/backend_nl.json b/backend/i18n/source/backend_nl.json index 91ecc2ab8..722021f95 100644 --- a/backend/i18n/source/backend_nl.json +++ b/backend/i18n/source/backend_nl.json @@ -18,8 +18,6 @@ "apps.languages.masterLanguageNotRemovable": "Hoofdtaal kan niet worden verwijderd.", "apps.nameAlreadyExists": "Er bestaat al een app met dezelfde naam.", "apps.notImage": "Bestand is geen afbeelding", - "apps.patterns.nameAlreadyExists": "Er bestaat al een patroon met dezelfde naam.", - "apps.patterns.patternAlreadyExists": "Dit patroon bestaat al maar met een andere naam.", "apps.plans.notFound": "Een plan met deze id bestaat niet.", "apps.plans.notPlanOwner": "Plan kan alleen worden gewijzigd van de gebruiker die het plan aanvankelijk heeft geconfigureerd.", "apps.roles.defaultRoleNotRemovable": "Kan een standaardrol niet verwijderen.", @@ -207,9 +205,6 @@ "history.apps.languagedRemoved": "verwijderde taal {[Language]}", "history.apps.languagedSetToMaster": "hoofdtaal gewijzigd in {[Language]}", "history.apps.languagedUpdated": "bijgewerkte taal {[Language]}", - "history.apps.patternAdded": "toegevoegd patroon {[Name]}", - "history.apps.patternDeleted": "verwijderd patroon {[PatternId]}", - "history.apps.patternUpdated": "bijgewerkt patroon {[Name]}", "history.apps.planChanged": "plan gewijzigd in {[Plan]}", "history.apps.planReset": "gereset plan", "history.apps.roleAdded": "toegevoegde rol {[Name]}", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index 5fb90b0f2..ea09a2bcb 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -52,6 +52,11 @@ "apps.uploadImageTooBig": "App image is too big.", "apps.welcomeSubtitle": "Welcome to Squidex.", "apps.welcomeTitle": "Hi {user}", + "appSettings.hideScheduler": "Hide dialog for scheduled publishing", + "appSettings.refreshTooltip": "Refresh UI Settings (CTRL + SHIFT + R)", + "appSettings.reloaded": "UI Settings reloaded.", + "appSettings.title": "UI Settings", + "appSettings.updateFailed": "Failed to update UI settings. Please reload.", "assets.createFolder": "Create Folder", "assets.createFolderFailed": "Failed to create asset folder. Please reload.", "assets.createFolderTooltip": "Create new folder (CTRL + SHIFT + G)", @@ -520,6 +525,10 @@ "dashboard.trafficSummaryCard": "API Traffic Summary", "dashboard.welcomeText": "Welcome to **{app}** dashboard.", "dashboard.welcomeTitle": "Hi {user}", + "editors.deleteConfirmText": "Do you really want to remove this Editor URL?", + "editors.deleteConfirmTitle": "Delete Editor URL", + "editors.empty": "No Editor URL created yet.", + "editors.title": "Custom Editors", "eventConsumers.count": "Count", "eventConsumers.loadFailed": "Failed to load event consumers. Please reload.", "eventConsumers.pageTitle": "Event Consumers", @@ -553,13 +562,8 @@ "notifo.subscripeTooltip": "Click this button to subscribe to all changes and to receive push notifications.", "patterns.deleteConfirmText": "Do you really want to remove this pattern?", "patterns.deleteConfirmTitle": "Delete pattern", - "patterns.deleteFailed": "Failed to remove pattern. Please reload.", "patterns.empty": "No pattern created yet.", - "patterns.loadFailed": "Failed to add pattern. Please reload.", - "patterns.nameValidationMessage": "Name can only contain letters, numbers, dashes and spaces.", - "patterns.refreshTooltip": "Refresh patterns (CTRL + SHIFT + R)", - "patterns.reloaded": "Patterns reloaded.", - "patterns.updateFailed": "Failed to update pattern. Please reload.", + "patterns.title": "Patterns", "plans.billingPortal": "Billing Portal", "plans.billingPortalHint": "Go to Billing Portal for payment history and subscription overview.", "plans.change": "Change", diff --git a/backend/i18n/source/frontend_it.json b/backend/i18n/source/frontend_it.json index 9fb1198ad..788332b03 100644 --- a/backend/i18n/source/frontend_it.json +++ b/backend/i18n/source/frontend_it.json @@ -547,13 +547,7 @@ "notifo.subscripeTooltip": "Fai clic su questo pulsante per iscriverti a tutte le modifiche e ricevere le notifiche push.", "patterns.deleteConfirmText": "Sei sicuro di voler rimuovere il pattern?", "patterns.deleteConfirmTitle": "Cancella il pattern", - "patterns.deleteFailed": "Non è stato possibile rimuovere il pattern. Per favore ricarica.", "patterns.empty": "Nessun pattern è stato ancora creato.", - "patterns.loadFailed": "Non è stato possibile aggiungere il pattern. Per favore ricarica.", - "patterns.nameValidationMessage": "Il nome può contenere solo lettere, numeri, trattini e spazi.", - "patterns.refreshTooltip": "Aggiorna i pattern (CTRL + SHIFT + R)", - "patterns.reloaded": "Pattern ricaricati.", - "patterns.updateFailed": "Non è stato possibile aggiornare pattern. Per favore ricarica.", "plans.billingPortal": "Portale di fatturazione", "plans.billingPortalHint": "Vai al portale di fatturazione per lo storico dei pagamenti e una panoramica per l'abbonamento.", "plans.change": "Cambia", diff --git a/backend/i18n/source/frontend_nl.json b/backend/i18n/source/frontend_nl.json index 6ba22de24..542821cb1 100644 --- a/backend/i18n/source/frontend_nl.json +++ b/backend/i18n/source/frontend_nl.json @@ -519,13 +519,7 @@ "notifo.subscripeTooltip": "Klik op deze knop om je te abonneren op alle wijzigingen en om pushmeldingen te ontvangen.", "patterns.deleteConfirmText": "Wil je dit patroon echt verwijderen?", "patterns.deleteConfirmTitle": "Verwijder patroon", - "patterns.deleteFailed": "Verwijderen van patroon is mislukt. Laad opnieuw.", "patterns.empty": "Nog geen patroon gemaakt.", - "patterns.loadFailed": "Toevoegen van patroon is mislukt. Laad opnieuw.", - "patterns.nameValidationMessage": "Naam mag alleen letters, cijfers, streepjes en spaties bevatten.", - "patterns.refreshTooltip": "Ververs patronen (CTRL + SHIFT + R)", - "patterns.reloaded": "Patronen herladen.", - "patterns.updateFailed": "Bijwerken van patroon is mislukt. Laad opnieuw.", "plans.billingPortal": "Factureringsportal", "plans.billingPortalHint": "Ga naar het factureringsportaal voor betalingsgeschiedenis en abonnementsoverzicht.", "plans.change": "Wijzigen", diff --git a/backend/src/Migrations/MigrationPath.cs b/backend/src/Migrations/MigrationPath.cs index 24a8eb610..4c0085cb0 100644 --- a/backend/src/Migrations/MigrationPath.cs +++ b/backend/src/Migrations/MigrationPath.cs @@ -18,7 +18,7 @@ namespace Migrations { public sealed class MigrationPath : IMigrationPath { - private const int CurrentVersion = 25; + private const int CurrentVersion = 26; private readonly IServiceProvider serviceProvider; public MigrationPath(IServiceProvider serviceProvider) @@ -136,6 +136,12 @@ namespace Migrations yield return serviceProvider.GetRequiredService(); } + // Version 26: UI Settings. + if (version < 26) + { + yield return serviceProvider.GetRequiredService(); + } + yield return serviceProvider.GetRequiredService(); } } diff --git a/backend/src/Migrations/Migrations/CreateAppSettings.cs b/backend/src/Migrations/Migrations/CreateAppSettings.cs new file mode 100644 index 000000000..acd5f1b7e --- /dev/null +++ b/backend/src/Migrations/Migrations/CreateAppSettings.cs @@ -0,0 +1,112 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Migrations.OldEvents; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Events.Apps; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Migrations; + +#pragma warning disable CS0618 // Type or member is obsolete + +namespace Migrations.Migrations +{ + public sealed class CreateAppSettings : IMigration + { + private readonly ICommandBus commandBus; + private readonly IEventDataFormatter eventDataFormatter; + private readonly IEventStore eventStore; + + public CreateAppSettings(ICommandBus commandBus, + IEventDataFormatter eventDataFormatter, + IEventStore eventStore) + { + this.commandBus = commandBus; + this.eventDataFormatter = eventDataFormatter; + this.eventStore = eventStore; + } + + public async Task UpdateAsync() + { + var apps = new Dictionary, Dictionary>(); + + await eventStore.QueryAsync(storedEvent => + { + var @event = eventDataFormatter.ParseIfKnown(storedEvent); + + if (@event != null) + { + switch (@event.Payload) + { + case AppPatternAdded patternAdded: + { + var patterns = apps.GetOrAddNew(patternAdded.AppId); + + patterns[patternAdded.PatternId] = (patternAdded.Name, patternAdded.Pattern, patternAdded.Message); + break; + } + + case AppPatternUpdated patternUpdated: + { + var patterns = apps.GetOrAddNew(patternUpdated.AppId); + + patterns[patternUpdated.PatternId] = (patternUpdated.Name, patternUpdated.Pattern, patternUpdated.Message); + break; + } + + case AppPatternDeleted patternDeleted: + { + var patterns = apps.GetOrAddNew(patternDeleted.AppId); + + patterns.Remove(patternDeleted.PatternId); + break; + } + + case AppArchived appArchived: + { + apps.Remove(appArchived.AppId); + break; + } + } + } + + return Task.CompletedTask; + }, "^app\\-"); + + var actor = RefToken.Client("Migrator"); + + foreach (var (appId, patterns) in apps) + { + if (patterns.Count > 0) + { + var settings = new AppSettings + { + Patterns = patterns.Values.Select(x => new Pattern(x.Name, x.Pattern) + { + Message = x.Message + }).ToReadOnlyCollection() + }; + + await commandBus.PublishAsync(new UpdateAppSettings + { + AppId = appId, + Settings = settings, + FromRule = true, + Actor = actor + }); + } + } + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs b/backend/src/Migrations/OldEvents/AppPatternAdded.cs similarity index 86% rename from backend/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs rename to backend/src/Migrations/OldEvents/AppPatternAdded.cs index 7756bebd5..39adac313 100644 --- a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs +++ b/backend/src/Migrations/OldEvents/AppPatternAdded.cs @@ -5,12 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; +using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Events.Apps +namespace Migrations.OldEvents { [EventType(nameof(AppPatternAdded))] + [Obsolete("New Event introduced")] public sealed class AppPatternAdded : AppEvent { public DomainId PatternId { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs b/backend/src/Migrations/OldEvents/AppPatternDeleted.cs similarity index 83% rename from backend/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs rename to backend/src/Migrations/OldEvents/AppPatternDeleted.cs index 09988ba1e..31266f9ad 100644 --- a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs +++ b/backend/src/Migrations/OldEvents/AppPatternDeleted.cs @@ -5,12 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; +using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Events.Apps +namespace Migrations.OldEvents { [EventType(nameof(AppPatternDeleted))] + [Obsolete("New Event introduced")] public sealed class AppPatternDeleted : AppEvent { public DomainId PatternId { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs b/backend/src/Migrations/OldEvents/AppPatternUpdated.cs similarity index 86% rename from backend/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs rename to backend/src/Migrations/OldEvents/AppPatternUpdated.cs index 92d5060e0..f0447db0d 100644 --- a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs +++ b/backend/src/Migrations/OldEvents/AppPatternUpdated.cs @@ -5,12 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; +using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Events.Apps +namespace Migrations.OldEvents { [EventType(nameof(AppPatternUpdated))] + [Obsolete("New Event introduced")] public sealed class AppPatternUpdated : AppEvent { public DomainId PatternId { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs deleted file mode 100644 index 64b8bcb5e..000000000 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs +++ /dev/null @@ -1,71 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Collections; - -namespace Squidex.Domain.Apps.Core.Apps -{ - public sealed class AppPatterns : ImmutableDictionary - { - public static readonly AppPatterns Empty = new AppPatterns(); - - private AppPatterns() - { - } - - public AppPatterns(Dictionary inner) - : base(inner) - { - } - - [Pure] - public AppPatterns Remove(DomainId id) - { - return Without(id); - } - - [Pure] - public AppPatterns Add(DomainId id, string name, string pattern, string? message = null) - { - Guard.NotNullOrEmpty(name, nameof(name)); - Guard.NotNullOrEmpty(pattern, nameof(pattern)); - - var newPattern = new AppPattern(name, pattern) { Message = message }; - - return With(id, newPattern); - } - - [Pure] - public AppPatterns Update(DomainId id, string? name = null, string? pattern = null, string? message = null) - { - if (!TryGetValue(id, out var appPattern)) - { - return this; - } - - var newPattern = appPattern with - { - Message = message - }; - - if (!string.IsNullOrWhiteSpace(name)) - { - newPattern = newPattern with { Name = name }; - } - - if (!string.IsNullOrWhiteSpace(pattern)) - { - newPattern = newPattern with { Pattern = pattern }; - } - - return With(id, newPattern); - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppSettings.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppSettings.cs new file mode 100644 index 000000000..a125eb317 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppSettings.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.ObjectModel; +using Squidex.Infrastructure.Collections; + +namespace Squidex.Domain.Apps.Core.Apps +{ + [Equals(DoNotAddEqualityOperators = true)] + public sealed class AppSettings + { + public static readonly AppSettings Empty = new AppSettings(); + + public ReadOnlyCollection Patterns { get; init; } = ReadOnlyCollection.Empty(); + + public ReadOnlyCollection Editors { get; init; } = ReadOnlyCollection.Empty(); + + public bool HideScheduler { get; init; } + + public bool HideDateTimeModeButton { get; init; } + + public bool HideDateTimeQuickButtons { get; init; } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Editor.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Editor.cs new file mode 100644 index 000000000..5ec9ab71a --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Editor.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + +namespace Squidex.Domain.Apps.Core.Apps +{ + public sealed record Editor(string Name, string Url) + { + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsSurrogate.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsSurrogate.cs deleted file mode 100644 index 7540a4695..000000000 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsSurrogate.cs +++ /dev/null @@ -1,33 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Core.Apps.Json -{ - public sealed class AppPatternsSurrogate : Dictionary, ISurrogate - { - public void FromSource(AppPatterns source) - { - foreach (var (key, pattern) in source) - { - Add(key, pattern); - } - } - - public AppPatterns ToSource() - { - if (Count == 0) - { - return AppPatterns.Empty; - } - - return new AppPatterns(this); - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Pattern.cs similarity index 81% rename from backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Apps/Pattern.cs index 25f69b521..c4df66333 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Pattern.cs @@ -1,7 +1,7 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== @@ -9,8 +9,8 @@ namespace Squidex.Domain.Apps.Core.Apps { - public sealed record AppPattern(string Name, string Pattern) + public sealed record Pattern(string Name, string Regex) { public string? Message { get; init; } } -} +} \ No newline at end of file diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs index 615b13ce7..3317a153b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs @@ -12,7 +12,6 @@ using System.Linq; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Security; -using P = Squidex.Shared.Permissions; namespace Squidex.Domain.Apps.Core.Apps { @@ -21,17 +20,16 @@ namespace Squidex.Domain.Apps.Core.Apps { private static readonly HashSet ExtraPermissions = new HashSet { - P.AppComments, - P.AppContributorsRead, - P.AppHistory, - P.AppLanguagesRead, - P.AppPatternsRead, - P.AppPing, - P.AppRolesRead, - P.AppSchemasRead, - P.AppSearch, - P.AppTranslate, - P.AppUsage + Shared.Permissions.AppComments, + Shared.Permissions.AppContributorsRead, + Shared.Permissions.AppHistory, + Shared.Permissions.AppLanguagesRead, + Shared.Permissions.AppPing, + Shared.Permissions.AppRolesRead, + Shared.Permissions.AppSchemasRead, + Shared.Permissions.AppSearch, + Shared.Permissions.AppTranslate, + Shared.Permissions.AppUsage }; public const string Editor = "Editor"; @@ -93,7 +91,7 @@ namespace Squidex.Domain.Apps.Core.Apps if (Permissions.Any()) { - var prefix = P.ForApp(P.App, app).Id; + var prefix = Shared.Permissions.ForApp(Shared.Permissions.App, app).Id; foreach (var permission in Permissions) { @@ -105,7 +103,7 @@ namespace Squidex.Domain.Apps.Core.Apps { foreach (var extraPermissionId in ExtraPermissions) { - var extraPermission = P.ForApp(extraPermissionId, app); + var extraPermission = Shared.Permissions.ForApp(extraPermissionId, app); result.Add(extraPermission); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs index 50427c814..36d94f44b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs @@ -50,7 +50,6 @@ namespace Squidex.Domain.Apps.Core.Apps new PermissionSet( WithoutPrefix(Permissions.AppAssets), WithoutPrefix(Permissions.AppContents), - WithoutPrefix(Permissions.AppPatterns), WithoutPrefix(Permissions.AppRolesRead), WithoutPrefix(Permissions.AppRules), WithoutPrefix(Permissions.AppSchemas), diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs index 1cfe4391b..aad9aacff 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs @@ -6,8 +6,10 @@ // ========================================================================== using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; #pragma warning disable IDE0051 // Remove unused private members @@ -19,20 +21,20 @@ namespace Squidex.Domain.Apps.Core.Contents private const string DefaultName = "Unnamed"; public static readonly IReadOnlyDictionary EmptySteps = new Dictionary(); - public static readonly IReadOnlyList EmptySchemaIds = new List(); + public static readonly Workflow Default = CreateDefault(); - public static readonly Workflow Empty = new Workflow(default, EmptySteps); + public static readonly Workflow Empty = new Workflow(default, null); [IgnoreDuringEquals] public IReadOnlyDictionary Steps { get; } = EmptySteps; - public IReadOnlyList SchemaIds { get; } = EmptySchemaIds; + public ReadOnlyCollection SchemaIds { get; } = ReadOnlyCollection.Empty(); public Status Initial { get; } public Workflow( Status initial, - IReadOnlyDictionary? steps, + IReadOnlyDictionary? steps = null, IReadOnlyList? schemaIds = null, string? name = null) : base(name.Or(DefaultName)) @@ -46,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.Contents if (schemaIds != null) { - SchemaIds = schemaIds; + SchemaIds = schemaIds.ToReadOnlyCollection(); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs index f690286d1..59cb945dc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs @@ -52,14 +52,8 @@ namespace Squidex.Domain.Apps.Entities.Apps AddEventMessage( "history.apps.languagedSetToMaster"); - AddEventMessage( - "history.apps.patternAdded"); - - AddEventMessage( - "history.apps.patternDeleted"); - - AddEventMessage( - "history.apps.patternUpdated"); + AddEventMessage( + "history.apps.settingsUpdated"); AddEventMessage( "history.apps.roleAdded"); @@ -93,12 +87,6 @@ namespace Squidex.Domain.Apps.Entities.Apps return CreateLanguagesEvent(e, e.Language); case AppLanguageRemoved e: return CreateLanguagesEvent(e, e.Language); - case AppPatternAdded e: - return CreatePatternsEvent(e, e.PatternId, e.Name); - case AppPatternUpdated e: - return CreatePatternsEvent(e, e.PatternId, e.Name); - case AppPatternDeleted e: - return CreatePatternsEvent(e, e.PatternId); case AppRoleAdded e: return CreateRolesEvent(e, e.Name); case AppRoleUpdated e: @@ -109,11 +97,18 @@ namespace Squidex.Domain.Apps.Entities.Apps return CreatePlansEvent(e, e.PlanId); case AppPlanReset e: return CreatePlansEvent(e); + case AppSettingsUpdated e: + return CreateAppSettingsEvent(e); } return null; } + private HistoryEvent CreateAppSettingsEvent(AppSettingsUpdated e) + { + return ForEvent(e, "settings.appSettings"); + } + private HistoryEvent CreateContributorsEvent(IEvent e, string contributor, string? role = null) { return ForEvent(e, "settings.contributors").Param("Contributor", contributor).Param("Role", role); @@ -129,11 +124,6 @@ namespace Squidex.Domain.Apps.Entities.Apps return ForEvent(e, "settings.roles").Param("Name", name); } - private HistoryEvent CreatePatternsEvent(IEvent e, DomainId id, string? name = null) - { - return ForEvent(e, "settings.patterns").Param("PatternId", id).Param("Name", name); - } - private HistoryEvent CreateClientsEvent(IEvent e, string id) { return ForEvent(e, "settings.clients").Param("Id", id); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs index 412a8d662..9ed3dee7a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs @@ -63,9 +63,6 @@ namespace Squidex.Domain.Apps.Entities.Apps Search("Languages", Permissions.AppLanguagesRead, urlGenerator.LanguagesUI, SearchResultType.Setting); - Search("Patterns", Permissions.AppPatternsRead, - urlGenerator.PatternsUI, SearchResultType.Setting); - Search("Roles", Permissions.AppRolesRead, urlGenerator.RolesUI, SearchResultType.Setting); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs deleted file mode 100644 index 18ac2d6f9..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Apps.Commands -{ - public sealed class AddPattern : AppUpdateCommand - { - public DomainId PatternId { get; set; } - - public string Name { get; set; } - - public string Pattern { get; set; } - - public string? Message { get; set; } - - public AddPattern() - { - PatternId = DomainId.NewGuid(); - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateAppSettings.cs similarity index 55% rename from backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs rename to backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateAppSettings.cs index 2be0fbc2d..7093a4546 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateAppSettings.cs @@ -1,22 +1,16 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure; +using Squidex.Domain.Apps.Core.Apps; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class UpdatePattern : AppUpdateCommand + public sealed class UpdateAppSettings : AppUpdateCommand { - public DomainId PatternId { get; set; } - - public string Name { get; set; } - - public string Pattern { get; set; } - - public string? Message { get; set; } + public AppSettings Settings { get; set; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs index 44b88ff18..f8cab66ef 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs @@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject public AppClients Clients { get; set; } = AppClients.Empty; - public AppPatterns Patterns { get; set; } = AppPatterns.Empty; + public AppSettings Settings { get; set; } = AppSettings.Empty; public AppContributors Contributors { get; set; } = AppContributors.Empty; @@ -73,17 +73,20 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject return true; } - case AppImageUploaded e: - return UpdateImage(e, e => e.Image); - - case AppImageRemoved e when Image != null: - return UpdateImage(e, e => null); + case AppSettingsUpdated e when Is.Change(Settings, e.Settings): + return UpdateSettings(e.Settings); case AppPlanChanged e when Is.Change(Plan?.PlanId, e.PlanId): - return UpdatePlan(e, e => e.ToAppPlan()); + return UpdatePlan(e.ToPlan()); case AppPlanReset e when Plan != null: - return UpdatePlan(e, e => null); + return UpdatePlan(null); + + case AppImageUploaded e: + return UpdateImage(e.Image); + + case AppImageRemoved e when Image != null: + return UpdateImage(null); case AppContributorAssigned e: return UpdateContributors(e, (e, c) => c.Assign(e.ContributorId, e.Role)); @@ -109,15 +112,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject case AppWorkflowDeleted e: return UpdateWorkflows(e, (e, w) => w.Remove(e.WorkflowId)); - case AppPatternAdded e: - return UpdatePatterns(e, (ev, p) => p.Add(ev.PatternId, ev.Name, ev.Pattern, ev.Message)); - - case AppPatternDeleted e: - return UpdatePatterns(e, (e, p) => p.Remove(e.PatternId)); - - case AppPatternUpdated e: - return UpdatePatterns(e, (e, p) => p.Update(e.PatternId, e.Name, e.Pattern, e.Message)); - case AppRoleAdded e: return UpdateRoles(e, (e, r) => r.Add(e.Name)); @@ -186,15 +180,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject return !ReferenceEquals(previous, Languages); } - private bool UpdatePatterns(T @event, Func update) - { - var previous = Patterns; - - Patterns = update(@event, previous); - - return !ReferenceEquals(previous, Patterns); - } - private bool UpdateRoles(T @event, Func update) { var previous = Roles; @@ -213,16 +198,23 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject return !ReferenceEquals(previous, Workflows); } - private bool UpdateImage(T @event, Func update) + private bool UpdateImage(AppImage? image) + { + Image = image; + + return true; + } + + private bool UpdateSettings(AppSettings settings) { - Image = update(@event); + Settings = settings; return true; } - private bool UpdatePlan(T @event, Func update) + private bool UpdatePlan(AppPlan? plan) { - Plan = update(@event); + Plan = plan; return true; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs index 0986f980a..7cfa6c60b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs @@ -26,13 +26,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject { public sealed partial class AppDomainObject : DomainObject { - private readonly InitialPatterns initialPatterns; + private readonly InitialSettings initialSettings; private readonly IAppPlansProvider appPlansProvider; private readonly IAppPlanBillingManager appPlansBillingManager; private readonly IUserResolver userResolver; public AppDomainObject(IPersistenceFactory persistence, ISemanticLog log, - InitialPatterns initialPatterns, + InitialSettings initialPatterns, IAppPlansProvider appPlansProvider, IAppPlanBillingManager appPlansBillingManager, IUserResolver userResolver) @@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject this.userResolver = userResolver; this.appPlansProvider = appPlansProvider; this.appPlansBillingManager = appPlansBillingManager; - this.initialPatterns = initialPatterns; + this.initialSettings = initialPatterns; } protected override bool IsDeleted() @@ -88,6 +88,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject return Snapshot; }); + case UpdateAppSettings updateSettings: + return UpdateReturn(updateSettings, c => + { + GuardApp.CanUpdateSettings(c); + + UpdateSettings(c); + + return Snapshot; + }); + case UploadAppImage uploadImage: return UpdateReturn(uploadImage, c => { @@ -248,36 +258,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject return Snapshot; }); - case AddPattern addPattern: - return UpdateReturn(addPattern, c => - { - GuardAppPatterns.CanAdd(c, Snapshot); - - AddPattern(c); - - return Snapshot; - }); - - case DeletePattern deletePattern: - return UpdateReturn(deletePattern, c => - { - GuardAppPatterns.CanDelete(c, Snapshot); - - DeletePattern(c); - - return Snapshot; - }); - - case UpdatePattern updatePattern: - return UpdateReturn(updatePattern, c => - { - GuardAppPatterns.CanUpdate(c, Snapshot); - - UpdatePattern(c); - - return Snapshot; - }); - case ChangePlan changePlan: return UpdateReturnAsync(changePlan, async c => { @@ -339,10 +319,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject events.Add(CreateInitialOwner(command.Actor)); } - foreach (var (key, value) in initialPatterns) - { - events.Add(CreateInitialPattern(key, value)); - } + events.Add(CreateInitialSettings()); foreach (var @event in events) { @@ -369,6 +346,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject Raise(command, new AppUpdated()); } + private void UpdateSettings(UpdateAppSettings command) + { + Raise(command, new AppSettingsUpdated()); + } + private void UpdateClient(UpdateClient command) { Raise(command, new AppClientUpdated()); @@ -434,21 +416,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject Raise(command, new AppLanguageRemoved()); } - private void AddPattern(AddPattern command) - { - Raise(command, new AppPatternAdded()); - } - - private void DeletePattern(DeletePattern command) - { - Raise(command, new AppPatternDeleted()); - } - - private void UpdatePattern(UpdatePattern command) - { - Raise(command, new AppPatternUpdated()); - } - private void AddRole(AddRole command) { Raise(command, new AppRoleAdded()); @@ -493,15 +460,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject return new AppContributorAssigned { ContributorId = actor.Identifier, Role = Role.Owner }; } - private static AppPatternAdded CreateInitialPattern(DomainId id, AppPattern pattern) + private AppSettingsUpdated CreateInitialSettings() { - return new AppPatternAdded - { - Name = pattern.Name, - PatternId = id, - Pattern = pattern.Pattern, - Message = pattern.Message - }; + return new AppSettingsUpdated { Settings = initialSettings.Settings }; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs index b6fa6e6cb..d71f0f84e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs @@ -52,6 +52,72 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards Guard.NotNull(command, nameof(command)); } + public static void CanUpdateSettings(UpdateAppSettings command) + { + Guard.NotNull(command, nameof(command)); + + Validate.It(e => + { + var prefix = nameof(command.Settings); + + var settings = command.Settings; + + if (settings == null) + { + e(Not.Defined(nameof(settings)), prefix); + return; + } + + var patternsPrefix = $"{prefix}.{nameof(settings.Patterns)}"; + + if (settings.Patterns == null) + { + e(Not.Defined(nameof(settings.Patterns)), patternsPrefix); + } + else + { + settings.Patterns.Foreach((pattern, index) => + { + var patternPrefix = $"{patternsPrefix}[{index}]"; + + if (string.IsNullOrWhiteSpace(pattern.Name)) + { + e(Not.Defined(nameof(pattern.Name)), $"{patternPrefix}.{nameof(pattern.Name)}"); + } + + if (string.IsNullOrWhiteSpace(pattern.Regex)) + { + e(Not.Defined(nameof(pattern.Regex)), $"{patternPrefix}.{nameof(pattern.Regex)}"); + } + }); + } + + var editorsPrefix = $"{prefix}.{nameof(settings.Editors)}"; + + if (settings.Editors == null) + { + e(Not.Defined(nameof(settings.Editors)), editorsPrefix); + } + else + { + settings.Editors.Foreach((editor, index) => + { + var editorPrefix = $"{editorsPrefix}[{index}]"; + + if (string.IsNullOrWhiteSpace(editor.Name)) + { + e(Not.Defined(nameof(editor.Name)), $"{editorPrefix}.{nameof(editor.Name)}"); + } + + if (string.IsNullOrWhiteSpace(editor.Url)) + { + e(Not.Defined(nameof(editor.Url)), $"{editorPrefix}.{nameof(editor.Url)}"); + } + }); + } + }); + } + public static void CanChangePlan(ChangePlan command, IAppEntity app, IAppPlansProvider appPlans) { Guard.NotNull(command, nameof(command)); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppPatterns.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppPatterns.cs deleted file mode 100644 index b5c0a2b29..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppPatterns.cs +++ /dev/null @@ -1,109 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Linq; -using Squidex.Domain.Apps.Entities.Apps.Commands; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Translations; -using Squidex.Infrastructure.Validation; - -namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards -{ - public static class GuardAppPatterns - { - public static void CanAdd(AddPattern command, IAppEntity app) - { - Guard.NotNull(command, nameof(command)); - - var patterns = app.Patterns; - - Validate.It(e => - { - if (command.PatternId == DomainId.Empty) - { - e(Not.Defined(nameof(command.PatternId)), nameof(command.PatternId)); - } - - if (string.IsNullOrWhiteSpace(command.Name)) - { - e(Not.Defined(nameof(command.Name)), nameof(command.Name)); - } - - if (patterns.Values.Any(x => x.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) - { - e(T.Get("apps.patterns.nameAlreadyExists")); - } - - if (string.IsNullOrWhiteSpace(command.Pattern)) - { - e(Not.Defined(nameof(command.Pattern)), nameof(command.Pattern)); - } - else if (!command.Pattern.IsValidRegex()) - { - e(Not.Valid(nameof(command.Pattern)), nameof(command.Pattern)); - } - - if (patterns.Values.Any(x => x.Pattern == command.Pattern)) - { - e(T.Get("apps.patterns.patternAlreadyExists")); - } - }); - } - - public static void CanDelete(DeletePattern command, IAppEntity app) - { - Guard.NotNull(command, nameof(command)); - - var patterns = app.Patterns; - - if (!patterns.ContainsKey(command.PatternId)) - { - throw new DomainObjectNotFoundException(command.PatternId.ToString()); - } - } - - public static void CanUpdate(UpdatePattern command, IAppEntity app) - { - Guard.NotNull(command, nameof(command)); - - var patterns = app.Patterns; - - if (!patterns.ContainsKey(command.PatternId)) - { - throw new DomainObjectNotFoundException(command.PatternId.ToString()); - } - - Validate.It(e => - { - if (string.IsNullOrWhiteSpace(command.Name)) - { - e(Not.Defined(nameof(command.Name)), nameof(command.Name)); - } - - if (patterns.Any(x => x.Key != command.PatternId && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) - { - e(T.Get("apps.patterns.nameAlreadyExists")); - } - - if (string.IsNullOrWhiteSpace(command.Pattern)) - { - e(Not.Defined(nameof(command.Pattern)), nameof(command.Pattern)); - } - else if (!command.Pattern.IsValidRegex()) - { - e(Not.Valid(nameof(command.Pattern)), nameof(command.Pattern)); - } - - if (patterns.Any(x => x.Key != command.PatternId && x.Value.Pattern == command.Pattern)) - { - e(T.Get("apps.patterns.patternAlreadyExists")); - } - }); - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs index 318344109..387ab3040 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Apps AppClients Clients { get; } - AppPatterns Patterns { get; } + AppSettings Settings { get; } AppContributors Contributors { get; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs deleted file mode 100644 index f839c26ea..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Apps -{ - public sealed class InitialPatterns : Dictionary - { - public InitialPatterns() - { - } - - public InitialPatterns(Dictionary patterns) - : base(patterns) - { - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/InitialSettings.cs similarity index 67% rename from backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs rename to backend/src/Squidex.Domain.Apps.Entities/Apps/InitialSettings.cs index bd778377c..f196b3af3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/InitialSettings.cs @@ -5,12 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure; +using Squidex.Domain.Apps.Core.Apps; -namespace Squidex.Domain.Apps.Entities.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps { - public sealed class DeletePattern : AppUpdateCommand + public sealed class InitialSettings { - public DomainId PatternId { get; set; } + public AppSettings Settings { get; set; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs index e47e415d4..1c88cb548 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs @@ -5,8 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; @@ -16,13 +18,39 @@ namespace Squidex.Domain.Apps.Entities.Apps { public sealed class RolePermissionsProvider { + private readonly List forAppSchemas = new List(); + private readonly List forAppWithoutSchemas = new List(); private readonly IAppProvider appProvider; + static RolePermissionsProvider() + { + } + public RolePermissionsProvider(IAppProvider appProvider) { Guard.NotNull(appProvider, nameof(appProvider)); this.appProvider = appProvider; + + foreach (var field in typeof(Permissions).GetFields(BindingFlags.Public | BindingFlags.Static)) + { + if (field.IsLiteral && !field.IsInitOnly) + { + var value = field.GetValue(null) as string; + + if (value?.StartsWith(Permissions.App, StringComparison.OrdinalIgnoreCase) == true) + { + if (value.IndexOf("{name}", Permissions.App.Length, StringComparison.OrdinalIgnoreCase) >= 0) + { + forAppSchemas.Add(value); + } + else + { + forAppWithoutSchemas.Add(value); + } + } + } + } } public async Task> GetPermissionsAsync(IAppEntity app) @@ -31,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = new List { Permission.Any }; - foreach (var permission in Permissions.ForAppsNonSchema) + foreach (var permission in forAppWithoutSchemas) { if (permission.Length > Permissions.App.Length + 1) { @@ -44,7 +72,7 @@ namespace Squidex.Domain.Apps.Entities.Apps } } - foreach (var permission in Permissions.ForAppsSchema) + foreach (var permission in forAppSchemas) { var trimmed = permission[(Permissions.App.Length + 1)..]; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Context.cs b/backend/src/Squidex.Domain.Apps.Entities/Context.cs index a964fe722..028b1d51c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Context.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Context.cs @@ -14,7 +14,6 @@ using Squidex.Infrastructure.Security; using Squidex.Shared; using Squidex.Shared.Identity; using ClaimsPermissions = Squidex.Infrastructure.Security.PermissionSet; -using P = Squidex.Shared.Permissions; namespace Squidex.Domain.Apps.Entities { @@ -61,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities var claimsIdentity = new ClaimsIdentity(); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); - claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, P.All)); + claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, Permissions.All)); return new Context(claimsPrincipal, app); } diff --git a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs b/backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs index e0354a233..e4ba8f0fb 100644 --- a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs +++ b/backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Events.Apps { public string PlanId { get; set; } - public AppPlan ToAppPlan() + public AppPlan ToPlan() { return new AppPlan(Actor, PlanId); } diff --git a/backend/src/Squidex.Domain.Apps.Events/Apps/AppSettingsUpdated.cs b/backend/src/Squidex.Domain.Apps.Events/Apps/AppSettingsUpdated.cs new file mode 100644 index 000000000..b79d4ef55 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Events/Apps/AppSettingsUpdated.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events.Apps +{ + [EventType(nameof(AppSettingsUpdated))] + public sealed class AppSettingsUpdated : AppEvent + { + public AppSettings Settings { get; set; } + } +} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs index 7932737a2..802fff4ed 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs @@ -190,17 +190,9 @@ namespace Squidex.Infrastructure.MongoDb var actionBlock = new ActionBlock(async x => { - try + if (!combined.IsCancellationRequested) { - if (!combined.IsCancellationRequested) - { - await processor(x); - } - } - catch (OperationCanceledException ex) - { - // Dataflow swallows operation cancelled exception. - throw new AggregateException(ex); + await processor(x); } }, new ExecutionDataflowBlockOptions diff --git a/backend/src/Squidex.Shared/PermissionExtensions.cs b/backend/src/Squidex.Shared/PermissionExtensions.cs new file mode 100644 index 000000000..fc64240fd --- /dev/null +++ b/backend/src/Squidex.Shared/PermissionExtensions.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Linq; +using Squidex.Infrastructure.Security; + +namespace Squidex.Shared +{ + public static class PermissionExtensions + { + public static bool Allows(this PermissionSet permissions, string id, string app = Permission.Any, string schema = Permission.Any) + { + var permission = Permissions.ForApp(id, app, schema); + + return permissions.Allows(permission); + } + + public static string[] ToAppNames(this PermissionSet permissions) + { + var matching = permissions.Where(x => x.StartsWith("squidex.apps.")); + + var result = + matching + .Select(x => x.Id.Split('.')).Where(x => x.Length > 2) + .Select(x => x[2]) + .Distinct() + .ToArray(); + + return result; + } + } +} diff --git a/backend/src/Squidex.Shared/Permissions.cs b/backend/src/Squidex.Shared/Permissions.cs index 1b3097bac..bb646296e 100644 --- a/backend/src/Squidex.Shared/Permissions.cs +++ b/backend/src/Squidex.Shared/Permissions.cs @@ -5,10 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; @@ -16,32 +12,25 @@ namespace Squidex.Shared { public static class Permissions { - private static readonly List ForAppsNonSchemaList = new List(); - private static readonly List ForAppsSchemaList = new List(); - - public static IReadOnlyList ForAppsNonSchema - { - get => ForAppsNonSchemaList; - } - - public static IReadOnlyList ForAppsSchema - { - get => ForAppsSchemaList; - } - public const string All = "squidex.*"; public const string Admin = "squidex.admin.*"; + + // Orleans public const string AdminOrleans = "squidex.admin.orleans"; + // Admin App Creation public const string AdminAppCreate = "squidex.admin.apps.create"; + // Backup Admin public const string AdminRestore = "squidex.admin.restore"; + // Event Admin public const string AdminEvents = "squidex.admin.events"; public const string AdminEventsRead = "squidex.admin.events.read"; public const string AdminEventsManage = "squidex.admin.events.manage"; + // User Admin public const string AdminUsers = "squidex.admin.users"; public const string AdminUsersRead = "squidex.admin.users.read"; public const string AdminUsersCreate = "squidex.admin.users.create"; @@ -51,67 +40,84 @@ namespace Squidex.Shared public const string App = "squidex.apps.{app}"; + // App General public const string AppAdmin = "squidex.apps.{app}.*"; public const string AppDelete = "squidex.apps.{app}.delete"; public const string AppUpdate = "squidex.apps.{app}.update"; - public const string AppUpdateImage = "squidex.apps.{app}.image"; + public const string AppUpdateSettings = "squidex.apps.{app}.settings"; + + // App Image + public const string AppImageUpload = "squidex.apps.{app}.image"; + public const string AppImageDelete = "squidex.apps.{app}.image"; + // History public const string AppHistory = "squidex.apps.{app}.history"; + + // Ping public const string AppPing = "squidex.apps.{app}.ping"; + + // Search public const string AppSearch = "squidex.apps.{app}.search"; + + // Translate public const string AppTranslate = "squidex.apps.{app}.translate"; + + // Usage public const string AppUsage = "squidex.apps.{app}.usage"; + // Comments public const string AppComments = "squidex.apps.{app}.comments"; public const string AppCommentsRead = "squidex.apps.{app}.comments.read"; public const string AppCommentsCreate = "squidex.apps.{app}.comments.create"; public const string AppCommentsUpdate = "squidex.apps.{app}.comments.update"; public const string AppCommentsDelete = "squidex.apps.{app}.comments.delete"; + // Clients public const string AppClients = "squidex.apps.{app}.clients"; public const string AppClientsRead = "squidex.apps.{app}.clients.read"; public const string AppClientsCreate = "squidex.apps.{app}.clients.create"; public const string AppClientsUpdate = "squidex.apps.{app}.clients.update"; public const string AppClientsDelete = "squidex.apps.{app}.clients.delete"; + // Contributors public const string AppContributors = "squidex.apps.{app}.contributors"; public const string AppContributorsRead = "squidex.apps.{app}.contributors.read"; public const string AppContributorsAssign = "squidex.apps.{app}.contributors.assign"; public const string AppContributorsRevoke = "squidex.apps.{app}.contributors.revoke"; + // Languages public const string AppLanguages = "squidex.apps.{app}.languages"; public const string AppLanguagesRead = "squidex.apps.{app}.languages.read"; public const string AppLanguagesCreate = "squidex.apps.{app}.languages.create"; public const string AppLanguagesUpdate = "squidex.apps.{app}.languages.update"; public const string AppLanguagesDelete = "squidex.apps.{app}.languages.delete"; + // Roles public const string AppRoles = "squidex.apps.{app}.roles"; public const string AppRolesRead = "squidex.apps.{app}.roles.read"; public const string AppRolesCreate = "squidex.apps.{app}.roles.create"; public const string AppRolesUpdate = "squidex.apps.{app}.roles.update"; public const string AppRolesDelete = "squidex.apps.{app}.roles.delete"; - public const string AppPatterns = "squidex.apps.{app}.patterns"; - public const string AppPatternsRead = "squidex.apps.{app}.patterns.read"; - public const string AppPatternsCreate = "squidex.apps.{app}.patterns.create"; - public const string AppPatternsUpdate = "squidex.apps.{app}.patterns.update"; - public const string AppPatternsDelete = "squidex.apps.{app}.patterns.delete"; - + // Workflows public const string AppWorkflows = "squidex.apps.{app}.workflows"; public const string AppWorkflowsRead = "squidex.apps.{app}.workflows.read"; public const string AppWorkflowsCreate = "squidex.apps.{app}.workflows.create"; public const string AppWorkflowsUpdate = "squidex.apps.{app}.workflows.update"; public const string AppWorkflowsDelete = "squidex.apps.{app}.workflows.delete"; + // Backups public const string AppBackups = "squidex.apps.{app}.backups"; public const string AppBackupsRead = "squidex.apps.{app}.backups.read"; public const string AppBackupsCreate = "squidex.apps.{app}.backups.create"; public const string AppBackupsDelete = "squidex.apps.{app}.backups.delete"; + // Plans public const string AppPlans = "squidex.apps.{app}.plans"; public const string AppPlansRead = "squidex.apps.{app}.plans.read"; public const string AppPlansChange = "squidex.apps.{app}.plans.change"; + // Assets public const string AppAssets = "squidex.apps.{app}.assets"; public const string AppAssetsRead = "squidex.apps.{app}.assets.read"; public const string AppAssetsCreate = "squidex.apps.{app}.assets.create"; @@ -119,6 +125,7 @@ namespace Squidex.Shared public const string AppAssetsUpdate = "squidex.apps.{app}.assets.update"; public const string AppAssetsDelete = "squidex.apps.{app}.assets.delete"; + // Rules public const string AppRules = "squidex.apps.{app}.rules"; public const string AppRulesRead = "squidex.apps.{app}.rules.read"; public const string AppRulesEvents = "squidex.apps.{app}.rules.events"; @@ -127,6 +134,7 @@ namespace Squidex.Shared public const string AppRulesDisable = "squidex.apps.{app}.rules.disable"; public const string AppRulesDelete = "squidex.apps.{app}.rules.delete"; + // Schemas public const string AppSchemas = "squidex.apps.{app}.schemas"; public const string AppSchemasRead = "squidex.apps.{app}.schemas.read"; public const string AppSchemasCreate = "squidex.apps.{app}.schemas.create"; @@ -135,6 +143,7 @@ namespace Squidex.Shared public const string AppSchemasPublish = "squidex.apps.{app}.schemas.{name}.publish"; public const string AppSchemasDelete = "squidex.apps.{app}.schemas.{name}.delete"; + // Contents public const string AppContents = "squidex.apps.{app}.contents.{name}"; public const string AppContentsRead = "squidex.apps.{app}.contents.{name}.read"; public const string AppContentsReadOwn = "squidex.apps.{app}.contents.{name}.read.own"; @@ -151,55 +160,11 @@ namespace Squidex.Shared public const string AppContentsDelete = "squidex.apps.{app}.contents.{name}.delete"; public const string AppContentsDeleteOwn = "squidex.apps.{app}.contents.{name}.delete.own"; - static Permissions() - { - foreach (var field in typeof(Permissions).GetFields(BindingFlags.Public | BindingFlags.Static)) - { - if (field.IsLiteral && !field.IsInitOnly) - { - var value = field.GetValue(null) as string; - - if (value?.StartsWith(App, StringComparison.OrdinalIgnoreCase) == true) - { - if (value.IndexOf("{name}", App.Length, StringComparison.OrdinalIgnoreCase) >= 0) - { - ForAppsSchemaList.Add(value); - } - else - { - ForAppsNonSchemaList.Add(value); - } - } - } - } - } - - public static bool Allows(this PermissionSet permissions, string id, string app = Permission.Any, string schema = Permission.Any) - { - var permission = ForApp(id, app, schema); - - return permissions.Allows(permission); - } - public static Permission ForApp(string id, string app = Permission.Any, string schema = Permission.Any) { Guard.NotNull(id, nameof(id)); return new Permission(id.Replace("{app}", app ?? Permission.Any).Replace("{name}", schema ?? Permission.Any)); } - - public static string[] ToAppNames(this PermissionSet permissions) - { - var matching = permissions.Where(x => x.StartsWith("squidex.apps.")); - - var result = - matching - .Select(x => x.Id.Split('.')).Where(x => x.Length > 2) - .Select(x => x[2]) - .Distinct() - .ToArray(); - - return result; - } } } diff --git a/backend/src/Squidex.Shared/Texts.it.resx b/backend/src/Squidex.Shared/Texts.it.resx index 8bb3b6cf5..befa49727 100644 --- a/backend/src/Squidex.Shared/Texts.it.resx +++ b/backend/src/Squidex.Shared/Texts.it.resx @@ -139,12 +139,6 @@ Il file non è una immagine - - Esiste già un pattern con lo stesso nome. - - - Questo pattern esiste già con un altro nome. - Non esiste un piano con questo id. @@ -721,15 +715,6 @@ aggiornata la lingua {[Language]} - - ha aggiunto pattern {[Name]} - - - ha eliminato pattern {[PatternId]} - - - ha modificato pattern {[Name]} - ha cambiato il piano in {[Plan]} @@ -745,6 +730,9 @@ ha aggiornato il ruolo {[Name]} + + updated UI settings + ha sostituito la risorsa. diff --git a/backend/src/Squidex.Shared/Texts.nl.resx b/backend/src/Squidex.Shared/Texts.nl.resx index 90f4eec3e..020ab2f1f 100644 --- a/backend/src/Squidex.Shared/Texts.nl.resx +++ b/backend/src/Squidex.Shared/Texts.nl.resx @@ -139,12 +139,6 @@ Bestand is geen afbeelding - - Er bestaat al een patroon met dezelfde naam. - - - Dit patroon bestaat al maar met een andere naam. - Een plan met deze id bestaat niet. @@ -721,15 +715,6 @@ bijgewerkte taal {[Language]} - - toegevoegd patroon {[Name]} - - - verwijderd patroon {[PatternId]} - - - bijgewerkt patroon {[Name]} - plan gewijzigd in {[Plan]} @@ -745,6 +730,9 @@ bijgewerkte rol {[Name]} + + updated UI settings + item vervangen. diff --git a/backend/src/Squidex.Shared/Texts.resx b/backend/src/Squidex.Shared/Texts.resx index 948e8d6c1..680ccf20d 100644 --- a/backend/src/Squidex.Shared/Texts.resx +++ b/backend/src/Squidex.Shared/Texts.resx @@ -139,12 +139,6 @@ File is not an image - - A pattern with the same name already exists. - - - This pattern already exists but with another name. - A plan with this id does not exist. @@ -721,15 +715,6 @@ updated language {[Language]} - - added pattern {[Name]} - - - deleted pattern {[PatternId]} - - - updated pattern {[Name]} - changed plan to {[Plan]} @@ -745,6 +730,9 @@ updated role {[Name]} + + updated UI settings + replaced asset. diff --git a/backend/src/Squidex.Web/Resources.cs b/backend/src/Squidex.Web/Resources.cs index dc62f9b6f..9e06d61f6 100644 --- a/backend/src/Squidex.Web/Resources.cs +++ b/backend/src/Squidex.Web/Resources.cs @@ -11,7 +11,7 @@ using Lazy; using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; -using P = Squidex.Shared.Permissions; +using Squidex.Shared; namespace Squidex.Web { @@ -20,155 +20,148 @@ namespace Squidex.Web private readonly Dictionary<(string, string), bool> schemaPermissions = new Dictionary<(string, string), bool>(); // Contents - public bool CanReadContent(string schema) => IsAllowedForSchema(P.AppContentsReadOwn, schema); + public bool CanReadContent(string schema) => IsAllowedForSchema(Permissions.AppContentsReadOwn, schema); - public bool CanCreateContent(string schema) => IsAllowedForSchema(P.AppContentsCreate, schema); + public bool CanCreateContent(string schema) => IsAllowedForSchema(Permissions.AppContentsCreate, schema); - public bool CanCreateContentVersion(string schema) => IsAllowedForSchema(P.AppContentsVersionCreateOwn, schema); + public bool CanCreateContentVersion(string schema) => IsAllowedForSchema(Permissions.AppContentsVersionCreateOwn, schema); - public bool CanDeleteContent(string schema) => IsAllowedForSchema(P.AppContentsDeleteOwn, schema); + public bool CanDeleteContent(string schema) => IsAllowedForSchema(Permissions.AppContentsDeleteOwn, schema); - public bool CanDeleteContentVersion(string schema) => IsAllowedForSchema(P.AppContentsVersionDeleteOwn, schema); + public bool CanDeleteContentVersion(string schema) => IsAllowedForSchema(Permissions.AppContentsVersionDeleteOwn, schema); - public bool CanUpdateContent(string schema) => IsAllowedForSchema(P.AppContentsUpdateOwn, schema); + public bool CanUpdateContent(string schema) => IsAllowedForSchema(Permissions.AppContentsUpdateOwn, schema); // Schemas - public bool CanUpdateSchema(string schema) => IsAllowedForSchema(P.AppSchemasDelete, schema); + public bool CanUpdateSchema(string schema) => IsAllowedForSchema(Permissions.AppSchemasDelete, schema); - public bool CanUpdateSchemaScripts(string schema) => IsAllowedForSchema(P.AppSchemasScripts, schema); + public bool CanUpdateSchemaScripts(string schema) => IsAllowedForSchema(Permissions.AppSchemasScripts, schema); - public bool CanPublishSchema(string schema) => IsAllowedForSchema(P.AppSchemasPublish, schema); + public bool CanPublishSchema(string schema) => IsAllowedForSchema(Permissions.AppSchemasPublish, schema); - public bool CanDeleteSchema(string schema) => IsAllowedForSchema(P.AppSchemasDelete, schema); + public bool CanDeleteSchema(string schema) => IsAllowedForSchema(Permissions.AppSchemasDelete, schema); [Lazy] - public bool CanCreateSchema => IsAllowed(P.AppSchemasCreate); + public bool CanCreateSchema => IsAllowed(Permissions.AppSchemasCreate); + + [Lazy] + public bool CanUpdateSettings => IsAllowed(Permissions.AppUpdateSettings); // Contributors [Lazy] - public bool CanAssignContributor => IsAllowed(P.AppContributorsAssign); + public bool CanAssignContributor => IsAllowed(Permissions.AppContributorsAssign); [Lazy] - public bool CanRevokeContributor => IsAllowed(P.AppContributorsRevoke); + public bool CanRevokeContributor => IsAllowed(Permissions.AppContributorsRevoke); // Workflows [Lazy] - public bool CanCreateWorkflow => IsAllowed(P.AppWorkflowsCreate); + public bool CanCreateWorkflow => IsAllowed(Permissions.AppWorkflowsCreate); [Lazy] - public bool CanUpdateWorkflow => IsAllowed(P.AppWorkflowsUpdate); + public bool CanUpdateWorkflow => IsAllowed(Permissions.AppWorkflowsUpdate); [Lazy] - public bool CanDeleteWorkflow => IsAllowed(P.AppWorkflowsDelete); + public bool CanDeleteWorkflow => IsAllowed(Permissions.AppWorkflowsDelete); // Roles [Lazy] - public bool CanCreateRole => IsAllowed(P.AppRolesCreate); + public bool CanCreateRole => IsAllowed(Permissions.AppRolesCreate); [Lazy] - public bool CanUpdateRole => IsAllowed(P.AppRolesUpdate); + public bool CanUpdateRole => IsAllowed(Permissions.AppRolesUpdate); [Lazy] - public bool CanDeleteRole => IsAllowed(P.AppRolesDelete); + public bool CanDeleteRole => IsAllowed(Permissions.AppRolesDelete); // Languages [Lazy] - public bool CanCreateLanguage => IsAllowed(P.AppLanguagesCreate); - - [Lazy] - public bool CanUpdateLanguage => IsAllowed(P.AppLanguagesUpdate); - - [Lazy] - public bool CanDeleteLanguage => IsAllowed(P.AppLanguagesDelete); - - // Patterns - [Lazy] - public bool CanCreatePattern => IsAllowed(P.AppClientsCreate); + public bool CanCreateLanguage => IsAllowed(Permissions.AppLanguagesCreate); [Lazy] - public bool CanUpdatePattern => IsAllowed(P.AppPatternsUpdate); + public bool CanUpdateLanguage => IsAllowed(Permissions.AppLanguagesUpdate); [Lazy] - public bool CanDeletePattern => IsAllowed(P.AppPatternsDelete); + public bool CanDeleteLanguage => IsAllowed(Permissions.AppLanguagesDelete); // Clients [Lazy] - public bool CanCreateClient => IsAllowed(P.AppClientsCreate); + public bool CanCreateClient => IsAllowed(Permissions.AppClientsCreate); [Lazy] - public bool CanUpdateClient => IsAllowed(P.AppClientsUpdate); + public bool CanUpdateClient => IsAllowed(Permissions.AppClientsUpdate); [Lazy] - public bool CanDeleteClient => IsAllowed(P.AppClientsDelete); + public bool CanDeleteClient => IsAllowed(Permissions.AppClientsDelete); // Rules [Lazy] - public bool CanDisableRule => IsAllowed(P.AppRulesDisable); + public bool CanDisableRule => IsAllowed(Permissions.AppRulesDisable); [Lazy] - public bool CanCreateRule => IsAllowed(P.AppRulesCreate); + public bool CanCreateRule => IsAllowed(Permissions.AppRulesCreate); [Lazy] - public bool CanUpdateRule => IsAllowed(P.AppRulesUpdate); + public bool CanUpdateRule => IsAllowed(Permissions.AppRulesUpdate); [Lazy] - public bool CanDeleteRule => IsAllowed(P.AppRulesDelete); + public bool CanDeleteRule => IsAllowed(Permissions.AppRulesDelete); [Lazy] - public bool CanReadRuleEvents => IsAllowed(P.AppRulesEvents); + public bool CanReadRuleEvents => IsAllowed(Permissions.AppRulesEvents); // Users [Lazy] - public bool CanReadUsers => IsAllowed(P.AdminUsersRead); + public bool CanReadUsers => IsAllowed(Permissions.AdminUsersRead); [Lazy] - public bool CanCreateUser => IsAllowed(P.AdminUsersCreate); + public bool CanCreateUser => IsAllowed(Permissions.AdminUsersCreate); [Lazy] - public bool CanLockUser => IsAllowed(P.AdminUsersLock); + public bool CanLockUser => IsAllowed(Permissions.AdminUsersLock); [Lazy] - public bool CanUnlockUser => IsAllowed(P.AdminUsersUnlock); + public bool CanUnlockUser => IsAllowed(Permissions.AdminUsersUnlock); [Lazy] - public bool CanUpdateUser => IsAllowed(P.AdminUsersUpdate); + public bool CanUpdateUser => IsAllowed(Permissions.AdminUsersUpdate); // Assets [Lazy] - public bool CanUploadAsset => IsAllowed(P.AppAssetsUpload); + public bool CanUploadAsset => IsAllowed(Permissions.AppAssetsUpload); [Lazy] - public bool CanCreateAsset => IsAllowed(P.AppAssetsCreate); + public bool CanCreateAsset => IsAllowed(Permissions.AppAssetsCreate); [Lazy] - public bool CanDeleteAsset => IsAllowed(P.AppAssetsDelete); + public bool CanDeleteAsset => IsAllowed(Permissions.AppAssetsDelete); [Lazy] - public bool CanUpdateAsset => IsAllowed(P.AppAssetsUpdate); + public bool CanUpdateAsset => IsAllowed(Permissions.AppAssetsUpdate); [Lazy] - public bool CanReadAssets => IsAllowed(P.AppAssetsRead); + public bool CanReadAssets => IsAllowed(Permissions.AppAssetsRead); // Events [Lazy] - public bool CanReadEvents => IsAllowed(P.AdminEventsRead); + public bool CanReadEvents => IsAllowed(Permissions.AdminEventsRead); [Lazy] - public bool CanManageEvents => IsAllowed(P.AdminEventsManage); + public bool CanManageEvents => IsAllowed(Permissions.AdminEventsManage); // Orleans [Lazy] - public bool CanReadOrleans => IsAllowed(P.AdminOrleans); + public bool CanReadOrleans => IsAllowed(Permissions.AdminOrleans); // Backups [Lazy] - public bool CanRestoreBackup => IsAllowed(P.AdminEventsRead); + public bool CanRestoreBackup => IsAllowed(Permissions.AdminEventsRead); [Lazy] - public bool CanCreateBackup => IsAllowed(P.AppBackupsCreate); + public bool CanCreateBackup => IsAllowed(Permissions.AppBackupsCreate); [Lazy] - public bool CanDeleteBackup => IsAllowed(P.AppBackupsDelete); + public bool CanDeleteBackup => IsAllowed(Permissions.AppBackupsDelete); [Lazy] public string? App => GetAppName(); @@ -228,7 +221,7 @@ namespace Squidex.Web } } - var permission = P.ForApp(id, app, schema); + var permission = Permissions.ForApp(id, app, schema); return Context.UserPermissions.Allows(permission) || additional?.Allows(permission) == true; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs deleted file mode 100644 index d39075d11..000000000 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ /dev/null @@ -1,151 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Net.Http.Headers; -using Squidex.Areas.Api.Controllers.Apps.Models; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Apps.Commands; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; -using Squidex.Shared; -using Squidex.Web; - -namespace Squidex.Areas.Api.Controllers.Apps -{ - /// - /// Manages and configures app patterns. - /// - [ApiExplorerSettings(GroupName = nameof(Apps))] - public sealed class AppPatternsController : ApiController - { - public AppPatternsController(ICommandBus commandBus) - : base(commandBus) - { - } - - /// - /// Get app patterns. - /// - /// The name of the app. - /// - /// 200 => Patterns returned. - /// 404 => App not found. - /// - /// - /// Gets all configured regex patterns for the app with the specified name. - /// - [HttpGet] - [Route("apps/{app}/patterns/")] - [ProducesResponseType(typeof(PatternsDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppPatternsRead)] - [ApiCosts(0)] - public IActionResult GetPatterns(string app) - { - var response = Deferred.Response(() => - { - return GetResponse(App); - }); - - Response.Headers[HeaderNames.ETag] = App.ToEtag(); - - return Ok(response); - } - - /// - /// Create a new app pattern. - /// - /// The name of the app. - /// Pattern to be added to the app. - /// - /// 201 => Pattern created. - /// 400 => Pattern request not valid. - /// 404 => App not found. - /// - [HttpPost] - [Route("apps/{app}/patterns/")] - [ProducesResponseType(typeof(PatternsDto), 201)] - [ApiPermissionOrAnonymous(Permissions.AppPatternsCreate)] - [ApiCosts(1)] - public async Task PostPattern(string app, [FromBody] UpdatePatternDto request) - { - var command = request.ToAddCommand(); - - var response = await InvokeCommandAsync(command); - - return CreatedAtAction(nameof(GetPatterns), new { app }, response); - } - - /// - /// Update an app pattern. - /// - /// The name of the app. - /// The id of the pattern to be updated. - /// Pattern to be updated for the app. - /// - /// 200 => Pattern updated. - /// 400 => Pattern request not valid. - /// 404 => Pattern or app not found. - /// - [HttpPut] - [Route("apps/{app}/patterns/{id}/")] - [ProducesResponseType(typeof(PatternsDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppPatternsUpdate)] - [ApiCosts(1)] - public async Task PutPattern(string app, DomainId id, [FromBody] UpdatePatternDto request) - { - var command = request.ToUpdateCommand(id); - - var response = await InvokeCommandAsync(command); - - return Ok(response); - } - - /// - /// Delete an app pattern. - /// - /// The name of the app. - /// The id of the pattern to be deleted. - /// - /// 200 => Pattern deleted. - /// 404 => Pattern or app not found. - /// - /// - /// Schemas using this pattern will still function using the same Regular Expression. - /// - [HttpDelete] - [Route("apps/{app}/patterns/{id}/")] - [ProducesResponseType(typeof(PatternsDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppPatternsDelete)] - [ApiCosts(1)] - public async Task DeletePattern(string app, DomainId id) - { - var command = new DeletePattern { PatternId = id }; - - var response = await InvokeCommandAsync(command); - - return Ok(response); - } - - private async Task InvokeCommandAsync(ICommand command) - { - var context = await CommandBus.PublishAsync(command); - - var result = context.Result(); - var response = GetResponse(result); - - return response; - } - - private PatternsDto GetResponse(IAppEntity result) - { - return PatternsDto.FromApp(result, Resources); - } - } -} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index c7219996b..8d5796b0a 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -38,24 +39,24 @@ namespace Squidex.Areas.Api.Controllers.Apps { private static readonly ResizeOptions ResizeOptions = new ResizeOptions { Width = 50, Height = 50, Mode = ResizeMode.Crop }; private readonly IAppImageStore appImageStore; + private readonly IAppPlansProvider appPlansProvider; + private readonly IAppProvider appProvider; private readonly IAssetStore assetStore; private readonly IAssetThumbnailGenerator assetThumbnailGenerator; - private readonly IAppProvider appProvider; - private readonly IAppPlansProvider appPlansProvider; public AppsController(ICommandBus commandBus, IAppImageStore appImageStore, - IAssetStore assetStore, - IAssetThumbnailGenerator assetThumbnailGenerator, + IAppPlansProvider appPlansProvider, IAppProvider appProvider, - IAppPlansProvider appPlansProvider) + IAssetStore assetStore, + IAssetThumbnailGenerator assetThumbnailGenerator) : base(commandBus) { this.appImageStore = appImageStore; + this.appPlansProvider = appPlansProvider; + this.appProvider = appProvider; this.assetStore = assetStore; this.assetThumbnailGenerator = assetThumbnailGenerator; - this.appProvider = appProvider; - this.appPlansProvider = appPlansProvider; } /// @@ -161,13 +162,58 @@ namespace Squidex.Areas.Api.Controllers.Apps [ProducesResponseType(typeof(AppDto), StatusCodes.Status200OK)] [ApiPermissionOrAnonymous(Permissions.AppUpdate)] [ApiCosts(0)] - public async Task UpdateApp(string app, [FromBody] UpdateAppDto request) + public async Task PutApp(string app, [FromBody] UpdateAppDto request) { var response = await InvokeCommandAsync(request.ToCommand()); return Ok(response); } + /// + /// Get the app settings. + /// + /// The name of the app to get the settings for. + /// + /// 200 => App settingsd returned. + /// 404 => App not found. + /// + [HttpGet] + [Route("apps/{app}/settings")] + [ProducesResponseType(typeof(AppSettingsDto), StatusCodes.Status200OK)] + [ApiPermissionOrAnonymous] + [ApiCosts(0)] + public IActionResult GetAppSettings(string app) + { + var response = Deferred.Response(() => + { + return AppSettingsDto.FromApp(App, Resources); + }); + + return Ok(response); + } + + /// + /// Update the app settings. + /// + /// The name of the app to update. + /// The values to update. + /// + /// 200 => App updated. + /// 400 => App request not valid. + /// 404 => App not found. + /// + [HttpPut] + [Route("apps/{app}/settings")] + [ProducesResponseType(typeof(AppSettingsDto), StatusCodes.Status200OK)] + [ApiPermissionOrAnonymous(Permissions.AppUpdate)] + [ApiCosts(0)] + public async Task PutAppSettings(string app, [FromBody] UpdateAppSettingsDto request) + { + var response = await InvokeCommandAsync(request.ToCommand(), x => AppSettingsDto.FromApp(x, Resources)); + + return Ok(response); + } + /// /// Upload the app image. /// @@ -181,7 +227,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpPost] [Route("apps/{app}/image")] [ProducesResponseType(typeof(AppDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppUpdateImage)] + [ApiPermissionOrAnonymous(Permissions.AppImageUpload)] [ApiCosts(0)] public async Task UploadImage(string app, IFormFile file) { @@ -272,7 +318,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpDelete] [Route("apps/{app}/image")] [ProducesResponseType(typeof(AppDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppUpdateImage)] + [ApiPermissionOrAnonymous(Permissions.AppImageDelete)] [ApiCosts(0)] public async Task DeleteImage(string app) { @@ -300,16 +346,24 @@ namespace Squidex.Areas.Api.Controllers.Apps return NoContent(); } - private async Task InvokeCommandAsync(ICommand command) + private Task InvokeCommandAsync(ICommand command) { - var context = await CommandBus.PublishAsync(command); + return InvokeCommandAsync(command, x => + { + var userOrClientId = HttpContext.User.UserOrClientId()!; - var userOrClientId = HttpContext.User.UserOrClientId()!; + var isFrontend = HttpContext.User.IsInClient(DefaultClients.Frontend); + + return AppDto.FromApp(x, userOrClientId, isFrontend, appPlansProvider, Resources); + }); + } - var isFrontend = HttpContext.User.IsInClient(DefaultClients.Frontend); + private async Task InvokeCommandAsync(ICommand command, Func converter) + { + var context = await CommandBus.PublishAsync(command); var result = context.Result(); - var response = AppDto.FromApp(result, userOrClientId, isFrontend, appPlansProvider, Resources); + var response = converter(result); return response; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index 1428e18f5..054773dcc 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -22,7 +22,6 @@ using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Validation; using Squidex.Web; -using P = Squidex.Shared.Permissions; #pragma warning disable RECS0033 // Convert 'if' to '||' expression @@ -122,12 +121,12 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models permissions = role.Permissions; } - if (resources.Includes(P.ForApp(P.AppContents, app.Name), permissions)) + if (resources.Includes(Shared.Permissions.ForApp(Shared.Permissions.AppContents, app.Name), permissions)) { result.CanAccessContent = true; } - if (resources.IsAllowed(P.AppPlansChange, app.Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppPlansChange, app.Name, additional: permissions)) { result.PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name; } @@ -151,88 +150,88 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models AddDeleteLink("leave", resources.Url(x => nameof(x.DeleteMyself), values)); } - if (resources.IsAllowed(P.AppDelete, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppDelete, Name, additional: permissions)) { AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteApp), values)); } - if (resources.IsAllowed(P.AppUpdate, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppUpdate, Name, additional: permissions)) { - AddPutLink("update", resources.Url(x => nameof(x.UpdateApp), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutApp), values)); } - if (resources.IsAllowed(P.AppAssetsRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppAssetsRead, Name, additional: permissions)) { AddGetLink("assets", resources.Url(x => nameof(x.GetAssets), values)); } - if (resources.IsAllowed(P.AppBackupsRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppBackupsRead, Name, additional: permissions)) { AddGetLink("backups", resources.Url(x => nameof(x.GetBackups), values)); } - if (resources.IsAllowed(P.AppClientsRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppClientsRead, Name, additional: permissions)) { AddGetLink("clients", resources.Url(x => nameof(x.GetClients), values)); } - if (resources.IsAllowed(P.AppContributorsRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppContributorsRead, Name, additional: permissions)) { AddGetLink("contributors", resources.Url(x => nameof(x.GetContributors), values)); } - if (resources.IsAllowed(P.AppLanguagesRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppLanguagesRead, Name, additional: permissions)) { AddGetLink("languages", resources.Url(x => nameof(x.GetLanguages), values)); } - if (resources.IsAllowed(P.AppPatternsRead, Name, additional: permissions)) - { - AddGetLink("patterns", resources.Url(x => nameof(x.GetPatterns), values)); - } - - if (resources.IsAllowed(P.AppPlansRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppPlansRead, Name, additional: permissions)) { AddGetLink("plans", resources.Url(x => nameof(x.GetPlans), values)); } - if (resources.IsAllowed(P.AppRolesRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppRolesRead, Name, additional: permissions)) { AddGetLink("roles", resources.Url(x => nameof(x.GetRoles), values)); } - if (resources.IsAllowed(P.AppRulesRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppRulesRead, Name, additional: permissions)) { AddGetLink("rules", resources.Url(x => nameof(x.GetRules), values)); } - if (resources.IsAllowed(P.AppSchemasRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppSchemasRead, Name, additional: permissions)) { AddGetLink("schemas", resources.Url(x => nameof(x.GetSchemas), values)); } - if (resources.IsAllowed(P.AppWorkflowsRead, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppWorkflowsRead, Name, additional: permissions)) { AddGetLink("workflows", resources.Url(x => nameof(x.GetWorkflows), values)); } - if (resources.IsAllowed(P.AppSchemasCreate, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppSchemasCreate, Name, additional: permissions)) { AddPostLink("schemas/create", resources.Url(x => nameof(x.PostSchema), values)); } - if (resources.IsAllowed(P.AppAssetsCreate, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppAssetsCreate, Name, additional: permissions)) { AddPostLink("assets/create", resources.Url(x => nameof(x.PostSchema), values)); } - if (resources.IsAllowed(P.AppUpdateImage, Name, additional: permissions)) + if (resources.IsAllowed(Shared.Permissions.AppImageUpload, Name, additional: permissions)) { AddPostLink("image/upload", resources.Url(x => nameof(x.UploadImage), values)); + } + if (resources.IsAllowed(Shared.Permissions.AppImageDelete, Name, additional: permissions)) + { AddDeleteLink("image/delete", resources.Url(x => nameof(x.DeleteImage), values)); } + AddGetLink("settings", resources.Url(x => nameof(x.GetAppSettings), values)); + return this; } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs new file mode 100644 index 000000000..f27ea3a25 --- /dev/null +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs @@ -0,0 +1,74 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Validation; +using Squidex.Web; + +namespace Squidex.Areas.Api.Controllers.Apps.Models +{ + public sealed class AppSettingsDto : Resource + { + /// + /// The configured app patterns. + /// + [LocalizedRequired] + public List Patterns { get; set; } + + /// + /// The configured UI editors. + /// + [LocalizedRequired] + public List Editors { get; set; } + + /// + /// Hide the scheduler for content items. + /// + public bool HideScheduler { get; set; } + + /// + /// The version of the app. + /// + public long Version { get; set; } + + public static AppSettingsDto FromApp(IAppEntity app, Resources resources) + { + var settings = app.Settings; + + var result = new AppSettingsDto + { + HideScheduler = settings.HideScheduler, + Patterns = + settings.Patterns + .Select(x => SimpleMapper.Map(x, new PatternDto())).ToList(), + Editors = + settings.Editors + .Select(x => SimpleMapper.Map(x, new EditorDto())).ToList(), + Version = app.Version + }; + + return result.CreateLinks(resources); + } + + private AppSettingsDto CreateLinks(Resources resources) + { + var values = new { app = resources.App }; + + AddSelfLink(resources.Url(x => nameof(x.GetAppSettings), values)); + + if (resources.CanUpdateSettings) + { + AddPutLink("update", resources.Url(x => nameof(x.PutAppSettings), values)); + } + + return this; + } + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/EditorDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/EditorDto.cs new file mode 100644 index 000000000..b4b23414b --- /dev/null +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/EditorDto.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.Validation; + +namespace Squidex.Areas.Api.Controllers.Apps.Models +{ + public sealed class EditorDto + { + /// + /// The name of the editor. + /// + [LocalizedRequired] + public string Name { get; set; } + + /// + /// The url to the editor. + /// + [LocalizedRequired] + public string Url { get; set; } + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs index 359068a34..75563366a 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs @@ -5,15 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; -using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Validation; -using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models { - public sealed class PatternDto : Resource + public sealed class PatternDto { /// /// Unique id of the pattern. @@ -30,35 +27,11 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// The regex pattern. /// [LocalizedRequired] - public string Pattern { get; set; } + public string Regex { get; set; } /// /// The regex message. /// public string? Message { get; set; } - - public static PatternDto FromPattern(DomainId id, AppPattern pattern, Resources resources) - { - var result = SimpleMapper.Map(pattern, new PatternDto { Id = id }); - - return result.CreateLinks(resources); - } - - private PatternDto CreateLinks(Resources resources) - { - var values = new { app = resources.App, id = Id }; - - if (resources.CanUpdatePattern) - { - AddPutLink("update", resources.Url(x => nameof(x.PutPattern), values)); - } - - if (resources.CanDeletePattern) - { - AddDeleteLink("delete", resources.Url(x => nameof(x.DeletePattern), values)); - } - - return this; - } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs deleted file mode 100644 index 9142ae6b1..000000000 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs +++ /dev/null @@ -1,47 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Linq; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Infrastructure.Validation; -using Squidex.Web; - -namespace Squidex.Areas.Api.Controllers.Apps.Models -{ - public sealed class PatternsDto : Resource - { - /// - /// The patterns. - /// - [LocalizedRequired] - public PatternDto[] Items { get; set; } - - public static PatternsDto FromApp(IAppEntity app, Resources resources) - { - var result = new PatternsDto - { - Items = app.Patterns.Select(x => PatternDto.FromPattern(x.Key, x.Value, resources)).ToArray() - }; - - return result.CreateLinks(resources); - } - - private PatternsDto CreateLinks(Resources resources) - { - var values = new { app = resources.App }; - - AddSelfLink(resources.Url(x => nameof(x.GetPatterns), values)); - - if (resources.CanCreatePattern) - { - AddPostLink("create", resources.Url(x => nameof(x.PostPattern), values)); - } - - return this; - } - } -} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateAppSettingsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateAppSettingsDto.cs new file mode 100644 index 000000000..dc79acfd8 --- /dev/null +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateAppSettingsDto.cs @@ -0,0 +1,54 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Infrastructure.Collections; + +namespace Squidex.Areas.Api.Controllers.Apps.Models +{ + public sealed class UpdateAppSettingsDto + { + /// + /// The configured app patterns. + /// + [Required] + public List Patterns { get; set; } + + /// + /// The configured UI editors. + /// + [Required] + public List Editors { get; set; } + + /// + /// Hide the scheduler for content items. + /// + public bool HideScheduler { get; set; } + + public UpdateAppSettings ToCommand() + { + return new UpdateAppSettings + { + Settings = new AppSettings + { + HideScheduler = HideScheduler, + Patterns = + Patterns?.Select(x => new Pattern(x.Name, x.Regex) + { + Message = x.Message + }).ToReadOnlyCollection()!, + Editors = + Editors?.Select(x => new Editor(x.Name, x.Url)).ToReadOnlyCollection()! + } + }; + } + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs deleted file mode 100644 index c79b9528e..000000000 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs +++ /dev/null @@ -1,44 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Entities.Apps.Commands; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Reflection; -using Squidex.Infrastructure.Validation; - -namespace Squidex.Areas.Api.Controllers.Apps.Models -{ - public class UpdatePatternDto - { - /// - /// The name of the suggestion. - /// - [LocalizedRequired] - public string Name { get; set; } - - /// - /// The regex pattern. - /// - [LocalizedRequired] - public string Pattern { get; set; } - - /// - /// The regex message. - /// - public string? Message { get; set; } - - public AddPattern ToAddCommand() - { - return SimpleMapper.Map(this, new AddPattern()); - } - - public UpdatePattern ToUpdateCommand(DomainId id) - { - return SimpleMapper.Map(this, new UpdatePattern { PatternId = id }); - } - } -} diff --git a/backend/src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs b/backend/src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs index 272515baf..e6fed28a6 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs @@ -68,6 +68,8 @@ namespace Squidex.Areas.Api.Controllers.News.Service } } + result.Features ??= new List(); + return result; } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/UI/MyUIOptions.cs b/backend/src/Squidex/Areas/Api/Controllers/UI/MyUIOptions.cs index 937e3151e..0f9bac50e 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/UI/MyUIOptions.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/UI/MyUIOptions.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Apps; namespace Squidex.Areas.Api.Controllers.UI { diff --git a/backend/src/Squidex/Config/Domain/AppsServices.cs b/backend/src/Squidex/Config/Domain/AppsServices.cs index c22cfe991..e538d5ba3 100644 --- a/backend/src/Squidex/Config/Domain/AppsServices.cs +++ b/backend/src/Squidex/Config/Domain/AppsServices.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Squidex.Areas.Api.Controllers.UI; @@ -14,7 +15,7 @@ using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.Search; -using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; namespace Squidex.Config.Domain { @@ -47,7 +48,7 @@ namespace Squidex.Config.Domain { var uiOptions = c.GetRequiredService>().Value; - var result = new InitialPatterns(); + var patterns = new List(); if (uiOptions.RegexSuggestions != null) { @@ -56,12 +57,18 @@ namespace Squidex.Config.Domain if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(value)) { - result[DomainId.NewGuid()] = new AppPattern(key, value); + patterns.Add(new Pattern(key, value)); } } } - return result; + return new InitialSettings + { + Settings = new AppSettings + { + Patterns = patterns.ToReadOnlyCollection() + } + }; }); } } diff --git a/backend/src/Squidex/Config/Domain/MigrationServices.cs b/backend/src/Squidex/Config/Domain/MigrationServices.cs index f3f47142f..4e2338e18 100644 --- a/backend/src/Squidex/Config/Domain/MigrationServices.cs +++ b/backend/src/Squidex/Config/Domain/MigrationServices.cs @@ -35,6 +35,9 @@ namespace Squidex.Config.Domain services.AddTransientAs() .As(); + services.AddTransientAs() + .As(); + services.AddTransientAs() .As(); diff --git a/backend/src/Squidex/Config/Domain/SerializationServices.cs b/backend/src/Squidex/Config/Domain/SerializationServices.cs index 168a8bc2e..c35faa67c 100644 --- a/backend/src/Squidex/Config/Domain/SerializationServices.cs +++ b/backend/src/Squidex/Config/Domain/SerializationServices.cs @@ -48,7 +48,6 @@ namespace Squidex.Config.Domain new StringEnumConverter(), new SurrogateConverter(), new SurrogateConverter(), - new SurrogateConverter(), new SurrogateConverter(), new SurrogateConverter, JsonFilterSurrogate>(), new SurrogateConverter(), diff --git a/backend/src/Squidex/appsettings.json b/backend/src/Squidex/appsettings.json index 900c4a141..9361f90e3 100644 --- a/backend/src/Squidex/appsettings.json +++ b/backend/src/Squidex/appsettings.json @@ -163,10 +163,6 @@ * Hide the Local/UTC button */ "hideDateTimeModeButton": false, - /* - * True to disable scheduled content status changed, for example when you have your own scheduling system. - */ - "disableScheduledChanges": false, /* * Show the exposed values as information on the apps overview page. diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs deleted file mode 100644 index 0ec8a6c00..000000000 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using FluentAssertions; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Infrastructure; -using Xunit; - -namespace Squidex.Domain.Apps.Core.Model.Apps -{ - public class AppPatternJsonTests - { - [Fact] - public void Should_serialize_and_deserialize() - { - var patterns = AppPatterns.Empty; - - var guid1 = DomainId.NewGuid(); - var guid2 = DomainId.NewGuid(); - var guid3 = DomainId.NewGuid(); - - patterns = patterns.Add(guid1, "Name1", "Pattern1", "Default"); - patterns = patterns.Add(guid2, "Name2", "Pattern2", "Default"); - patterns = patterns.Add(guid3, "Name3", "Pattern3", "Default"); - patterns = patterns.Update(guid2, "Name2 Update", "Pattern2 Update", "Default2"); - patterns = patterns.Update(guid3, "Name3 Update", "Pattern3 Update", "Default3"); - patterns = patterns.Remove(guid1); - - var serialized = patterns.SerializeAndDeserialize(); - - serialized.Should().BeEquivalentTo(patterns); - } - } -} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs deleted file mode 100644 index 1837562c2..000000000 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Linq; -using FluentAssertions; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Infrastructure; -using Xunit; - -#pragma warning disable SA1310 // Field names must not contain underscore - -namespace Squidex.Domain.Apps.Core.Model.Apps -{ - public class AppPatternsTests - { - private readonly AppPatterns patterns_0; - private readonly DomainId firstId = DomainId.NewGuid(); - private readonly DomainId id = DomainId.NewGuid(); - - public AppPatternsTests() - { - patterns_0 = AppPatterns.Empty.Add(firstId, "Default", "Default Pattern", "Message"); - } - - [Fact] - public void Should_add_pattern() - { - var patterns_1 = patterns_0.Add(id, "NewPattern", "New Pattern", "Message"); - - patterns_1[id].Should().BeEquivalentTo(new AppPattern("NewPattern", "New Pattern") with { Message = "Message" }); - } - - [Fact] - public void Should_do_nothing_if_adding_pattern_with_same_id() - { - var patterns_1 = patterns_0.Add(firstId, "Default", "Default Pattern", "Message"); - - Assert.Same(patterns_1, patterns_0); - } - - [Fact] - public void Should_update_pattern() - { - var patterns_1 = patterns_0.Update(firstId, "UpdatePattern", "Update Pattern"); - - patterns_1[firstId].Should().BeEquivalentTo(new AppPattern("UpdatePattern", "Update Pattern")); - } - - [Fact] - public void Should_return_same_patterns_if_pattern_is_updated_with_same_values() - { - var patterns_1 = patterns_0.Update(firstId, "Default", "Default Pattern", "Message"); - - Assert.Same(patterns_0, patterns_1); - } - - [Fact] - public void Should_return_same_patterns_if_pattern_to_update_not_found() - { - var patterns_1 = patterns_0.Update(id, "NewPattern", "NewPattern", "Message"); - - Assert.Same(patterns_0, patterns_1); - } - - [Fact] - public void Should_remove_pattern() - { - var patterns_1 = patterns_0.Add(id, "Name1", "Pattern1"); - var patterns_2 = patterns_1.Remove(firstId); - - Assert.Equal(id, patterns_2.Keys.Single()); - } - - [Fact] - public void Should_do_nothing_if_remove_pattern_not_found() - { - var patterns_1 = patterns_0.Remove(id); - - Assert.Same(patterns_0, patterns_1); - } - } -} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs index 6df89a16c..a71ffeda8 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs @@ -151,7 +151,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps Assert.False(Roles.IsDefault(firstRole)); } - [InlineData("Developer", 7)] + [InlineData("Developer", 6)] [InlineData("Editor", 4)] [InlineData("Reader", 2)] [InlineData("Owner", 1)] @@ -173,9 +173,9 @@ namespace Squidex.Domain.Apps.Core.Model.Apps Assert.Equal(permissionCount, result!.Permissions.Count); } - [InlineData("Developer", 17)] - [InlineData("Editor", 14)] - [InlineData("Reader", 13)] + [InlineData("Developer", 15)] + [InlineData("Editor", 13)] + [InlineData("Reader", 12)] [InlineData("Owner", 1)] [Theory] public void Should_add_extra_permissions_for_frontend_client(string name, int permissionCount) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs index 214c93c13..3c250bb17 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs @@ -59,7 +59,6 @@ namespace Squidex.Domain.Apps.Core.TestHelpers new StringEnumConverter(), new SurrogateConverter(), new SurrogateConverter(), - new SurrogateConverter(), new SurrogateConverter(), new SurrogateConverter, JsonFilterSurrogate>(), new SurrogateConverter(), diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppSettingsSearchSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppSettingsSearchSourceTests.cs index 1dee663cc..5e6df30d8 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppSettingsSearchSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppSettingsSearchSourceTests.cs @@ -94,23 +94,6 @@ namespace Squidex.Domain.Apps.Entities.Apps Assert.Empty(result); } - [Fact] - public async Task Should_return_patterns_result_if_matching_and_permission_given() - { - var permission = Permissions.ForApp(Permissions.AppPatternsRead, appId.Name); - - var ctx = ContextWithPermission(permission.Id); - - A.CallTo(() => urlGenerator.PatternsUI(appId)) - .Returns("patterns-url"); - - var result = await sut.SearchAsync("patterns", ctx); - - result.Should().BeEquivalentTo( - new SearchResults() - .Add("Patterns", SearchResultType.Setting, "patterns-url")); - } - [Fact] public async Task Should_not_return_patterns_result_if_user_has_no_permission() { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs index 5746b0d38..efb8c2612 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs @@ -37,10 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject private readonly string planIdFree = "free"; private readonly AppDomainObject sut; private readonly DomainId workflowId = DomainId.NewGuid(); - private readonly DomainId patternId1 = DomainId.NewGuid(); - private readonly DomainId patternId2 = DomainId.NewGuid(); - private readonly DomainId patternId3 = DomainId.NewGuid(); - private readonly InitialPatterns initialPatterns; + private readonly InitialSettings initialSettings; protected override DomainId Id { @@ -63,13 +60,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject A.CallTo(() => appPlansProvider.GetPlan(planIdPaid)) .Returns(new ConfigAppLimitsPlan { Id = planIdPaid, MaxContributors = 30 }); - initialPatterns = new InitialPatterns + // Create a non-empty setting, otherwise the event is not raised as it does not change the domain object. + initialSettings = new InitialSettings { - { patternId1, new AppPattern("Number", "[0-9]") }, - { patternId2, new AppPattern("Numbers", "[0-9]*") } + Settings = new AppSettings + { + HideScheduler = true + } }; - sut = new AppDomainObject(PersistenceFactory, A.Dummy(), initialPatterns, appPlansProvider, appPlansBillingManager, userResolver); + sut = new AppDomainObject(PersistenceFactory, A.Dummy(), initialSettings, appPlansProvider, appPlansBillingManager, userResolver); sut.Setup(Id); } @@ -97,8 +97,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject .ShouldHaveSameEvents( CreateEvent(new AppCreated { Name = AppName }), CreateEvent(new AppContributorAssigned { ContributorId = Actor.Identifier, Role = Role.Owner }), - CreateEvent(new AppPatternAdded { PatternId = patternId1, Name = "Number", Pattern = "[0-9]" }), - CreateEvent(new AppPatternAdded { PatternId = patternId2, Name = "Numbers", Pattern = "[0-9]*" }) + CreateEvent(new AppSettingsUpdated { Settings = initialSettings.Settings }) ); } @@ -115,9 +114,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject LastEvents .ShouldHaveSameEvents( - CreateEvent(new AppCreated { Name = AppName }, true), - CreateEvent(new AppPatternAdded { PatternId = patternId1, Name = "Number", Pattern = "[0-9]" }, true), - CreateEvent(new AppPatternAdded { PatternId = patternId2, Name = "Numbers", Pattern = "[0-9]*" }, true) + CreateEvent(new AppCreated { Name = AppName }, true), // Must be with client actor. + CreateEvent(new AppSettingsUpdated { Settings = initialSettings.Settings }, true) ); } @@ -137,7 +135,31 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject LastEvents .ShouldHaveSameEvents( - CreateEvent(new AppUpdated { Label = "my-label", Description = "my-description" }) + CreateEvent(new AppUpdated { Label = command.Label, Description = command.Description }) + ); + } + + [Fact] + public async Task UpdateSettings_should_create_event_and_update_settings() + { + var settings = new AppSettings + { + HideDateTimeQuickButtons = true + }; + + var command = new UpdateAppSettings { Settings = settings }; + + await ExecuteCreateAsync(); + + var result = await PublishIdempotentAsync(command); + + result.ShouldBeEquivalent(sut.Snapshot); + + Assert.Equal(settings, sut.Snapshot.Settings); + + LastEvents + .ShouldHaveSameEvents( + CreateEvent(new AppSettingsUpdated { Settings = settings }) ); } @@ -601,63 +623,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject ); } - [Fact] - public async Task AddPattern_should_create_events_and_add_pattern() - { - var command = new AddPattern { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" }; - - await ExecuteCreateAsync(); - - var result = await PublishAsync(command); - - result.ShouldBeEquivalent(sut.Snapshot); - - Assert.Equal(initialPatterns.Count + 1, sut.Snapshot.Patterns.Count); - - LastEvents - .ShouldHaveSameEvents( - CreateEvent(new AppPatternAdded { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" }) - ); - } - - [Fact] - public async Task DeletePattern_should_create_events_and_update_pattern() - { - var command = new DeletePattern { PatternId = patternId3 }; - - await ExecuteCreateAsync(); - await ExecuteAddPatternAsync(); - - var result = await PublishAsync(command); - - result.ShouldBeEquivalent(sut.Snapshot); - - Assert.Equal(initialPatterns.Count, sut.Snapshot.Patterns.Count); - - LastEvents - .ShouldHaveSameEvents( - CreateEvent(new AppPatternDeleted { PatternId = patternId3 }) - ); - } - - [Fact] - public async Task UpdatePattern_should_create_events_and_remove_pattern() - { - var command = new UpdatePattern { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" }; - - await ExecuteCreateAsync(); - await ExecuteAddPatternAsync(); - - var result = await PublishIdempotentAsync(command); - - result.ShouldBeEquivalent(sut.Snapshot); - - LastEvents - .ShouldHaveSameEvents( - CreateEvent(new AppPatternUpdated { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" }) - ); - } - [Fact] public async Task ArchiveApp_should_create_events_and_update_archived_flag() { @@ -690,11 +655,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject return PublishAsync(new UploadAppImage { File = new NoopAssetFile() }); } - private Task ExecuteAddPatternAsync() - { - return PublishAsync(new AddPattern { PatternId = patternId3, Name = "Name", Pattern = ".*" }); - } - private Task ExecuteAssignContributorAsync() { return PublishAsync(new AssignContributor { ContributorId = contributorId, Role = Role.Editor }); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppPatternsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppPatternsTests.cs deleted file mode 100644 index 5e8cba2b1..000000000 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppPatternsTests.cs +++ /dev/null @@ -1,192 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using FakeItEasy; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.Apps.Commands; -using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Validation; -using Xunit; - -#pragma warning disable SA1310 // Field names must not contain underscore - -namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards -{ - public class GuardAppPatternsTests : IClassFixture - { - private readonly DomainId patternId = DomainId.NewGuid(); - private readonly AppPatterns patterns_0 = AppPatterns.Empty; - - [Fact] - public void CanAdd_should_throw_exception_if_name_empty() - { - var command = new AddPattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_0)), - new ValidationError("Name is required.", "Name")); - } - - [Fact] - public void CanAdd_should_throw_exception_if_pattern_empty() - { - var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_0)), - new ValidationError("Pattern is required.", "Pattern")); - } - - [Fact] - public void CanAdd_should_throw_exception_if_pattern_not_valid() - { - var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_0)), - new ValidationError("Pattern is not a valid value.", "Pattern")); - } - - [Fact] - public void CanAdd_should_throw_exception_if_name_exists() - { - var patterns_1 = patterns_0.Add(DomainId.NewGuid(), "any", "[a-z]", "Message"); - - var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_1)), - new ValidationError("A pattern with the same name already exists.")); - } - - [Fact] - public void CanAdd_should_throw_exception_if_pattern_exists() - { - var patterns_1 = patterns_0.Add(DomainId.NewGuid(), "any", "[a-z]", "Message"); - - var command = new AddPattern { PatternId = patternId, Name = "other", Pattern = "[a-z]" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_1)), - new ValidationError("This pattern already exists but with another name.")); - } - - [Fact] - public void CanAdd_should_not_throw_exception_if_command_is_valid() - { - var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; - - GuardAppPatterns.CanAdd(command, App(patterns_0)); - } - - [Fact] - public void CanDelete_should_throw_exception_if_pattern_not_found() - { - var command = new DeletePattern { PatternId = patternId }; - - Assert.Throws(() => GuardAppPatterns.CanDelete(command, App(patterns_0))); - } - - [Fact] - public void CanDelete_should_not_throw_exception_if_command_is_valid() - { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new DeletePattern { PatternId = patternId }; - - GuardAppPatterns.CanDelete(command, App(patterns_1)); - } - - [Fact] - public void CanUpdate_should_throw_exception_if_name_empty() - { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new UpdatePattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_1)), - new ValidationError("Name is required.", "Name")); - } - - [Fact] - public void CanUpdate_should_throw_exception_if_pattern_empty() - { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_1)), - new ValidationError("Pattern is required.", "Pattern")); - } - - [Fact] - public void CanUpdate_should_throw_exception_if_pattern_not_valid() - { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_1)), - new ValidationError("Pattern is not a valid value.", "Pattern")); - } - - [Fact] - public void CanUpdate_should_throw_exception_if_name_exists() - { - var id1 = DomainId.NewGuid(); - var id2 = DomainId.NewGuid(); - - var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message"); - var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message"); - - var command = new UpdatePattern { PatternId = id2, Name = "Pattern1", Pattern = "[0-4]" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_2)), - new ValidationError("A pattern with the same name already exists.")); - } - - [Fact] - public void CanUpdate_should_throw_exception_if_pattern_exists() - { - var id1 = DomainId.NewGuid(); - var id2 = DomainId.NewGuid(); - - var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message"); - var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message"); - - var command = new UpdatePattern { PatternId = id2, Name = "Pattern2", Pattern = "[0-5]" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_2)), - new ValidationError("This pattern already exists but with another name.")); - } - - [Fact] - public void CanUpdate_should_throw_exception_if_pattern_does_not_exists() - { - var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; - - Assert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_0))); - } - - [Fact] - public void CanUpdate_should_not_throw_exception_if_pattern_exist_with_valid_command() - { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; - - GuardAppPatterns.CanUpdate(command, App(patterns_1)); - } - - private static IAppEntity App(AppPatterns patterns) - { - var app = A.Fake(); - - A.CallTo(() => app.Patterns) - .Returns(patterns); - - return app; - } - } -} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs index c3ce149b9..8857fcd63 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Validation; using Squidex.Shared.Users; using Xunit; @@ -127,6 +128,126 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards GuardApp.CanChangePlan(command, App(plan), appPlans); } + [Fact] + public void CanUpdateSettings_should_throw_exception_if_settings_is_null() + { + var command = new UpdateAppSettings(); + + ValidationAssert.Throws(() => GuardApp.CanUpdateSettings(command), + new ValidationError("Settings is required.", "Settings")); + } + + [Fact] + public void CanUpdateSettings_should_throw_exception_if_patterns_is_null() + { + var command = new UpdateAppSettings + { + Settings = new AppSettings + { + Patterns = null! + } + }; + + ValidationAssert.Throws(() => GuardApp.CanUpdateSettings(command), + new ValidationError("Patterns is required.", "Settings.Patterns")); + } + + [Fact] + public void CanUpdateSettings_should_throw_exception_if_patterns_has_null_name() + { + var command = new UpdateAppSettings + { + Settings = new AppSettings + { + Patterns = ReadOnlyCollection.Create( + new Pattern(null!, "[a-z]")) + } + }; + + ValidationAssert.Throws(() => GuardApp.CanUpdateSettings(command), + new ValidationError("Name is required.", "Settings.Patterns[0].Name")); + } + + [Fact] + public void CanUpdateSettings_should_throw_exception_if_patterns_has_null_regex() + { + var command = new UpdateAppSettings + { + Settings = new AppSettings + { + Patterns = ReadOnlyCollection.Create( + new Pattern("name", null!)) + } + }; + + ValidationAssert.Throws(() => GuardApp.CanUpdateSettings(command), + new ValidationError("Regex is required.", "Settings.Patterns[0].Regex")); + } + + [Fact] + public void CanUpdateSettings_should_throw_exception_if_editors_is_null() + { + var command = new UpdateAppSettings + { + Settings = new AppSettings + { + Editors = null! + } + }; + + ValidationAssert.Throws(() => GuardApp.CanUpdateSettings(command), + new ValidationError("Editors is required.", "Settings.Editors")); + } + + [Fact] + public void CanUpdateSettings_should_throw_exception_if_editors_has_null_name() + { + var command = new UpdateAppSettings + { + Settings = new AppSettings + { + Editors = ReadOnlyCollection.Create( + new Editor(null!, "[a-z]")) + } + }; + + ValidationAssert.Throws(() => GuardApp.CanUpdateSettings(command), + new ValidationError("Name is required.", "Settings.Editors[0].Name")); + } + + [Fact] + public void CanUpdateSettings_should_throw_exception_if_patterns_has_null_url() + { + var command = new UpdateAppSettings + { + Settings = new AppSettings + { + Editors = ReadOnlyCollection.Create( + new Editor("name", null!)) + } + }; + + ValidationAssert.Throws(() => GuardApp.CanUpdateSettings(command), + new ValidationError("Url is required.", "Settings.Editors[0].Url")); + } + + [Fact] + public void CanUpdateSettings_should_not_throw_exception_if_setting_is_valid() + { + var command = new UpdateAppSettings + { + Settings = new AppSettings + { + Patterns = ReadOnlyCollection.Create( + new Pattern("name", "[a-z]")), + Editors = ReadOnlyCollection.Create( + new Editor("name", "url/to/editor")) + } + }; + + GuardApp.CanUpdateSettings(command); + } + private static IAppEntity App(AppPlan? plan) { var app = A.Fake(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultWorkflowsValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultWorkflowsValidatorTests.cs index da4829b83..a3189aa86 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultWorkflowsValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultWorkflowsValidatorTests.cs @@ -60,8 +60,8 @@ namespace Squidex.Domain.Apps.Entities.Contents var workflows = Workflows.Empty .Add(id1, "workflow1") .Add(id2, "workflow2") - .Update(id1, new Workflow(default, Workflow.EmptySteps, new List { schemaId.Id })) - .Update(id2, new Workflow(default, Workflow.EmptySteps, new List { schemaId.Id })); + .Update(id1, new Workflow(default, null, new List { schemaId.Id })) + .Update(id2, new Workflow(default, null, new List { schemaId.Id })); var errors = await sut.ValidateAsync(appId.Id, workflows); @@ -79,8 +79,8 @@ namespace Squidex.Domain.Apps.Entities.Contents var workflows = Workflows.Empty .Add(id1, "workflow1") .Add(id2, "workflow2") - .Update(id1, new Workflow(default, Workflow.EmptySteps, new List { oldSchemaId })) - .Update(id2, new Workflow(default, Workflow.EmptySteps, new List { oldSchemaId })); + .Update(id1, new Workflow(default, null, new List { oldSchemaId })) + .Update(id2, new Workflow(default, null, new List { oldSchemaId })); var errors = await sut.ValidateAsync(appId.Id, workflows); @@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var workflows = Workflows.Empty .Add(id1, "workflow1") .Add(id2, "workflow2") - .Update(id1, new Workflow(default, Workflow.EmptySteps, new List { schemaId.Id })); + .Update(id1, new Workflow(default, null, new List { schemaId.Id })); var errors = await sut.ValidateAsync(appId.Id, workflows); diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs index fa3e59f1f..ede84e790 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs @@ -40,16 +40,16 @@ namespace TestSuite.ApiTests [Fact] public async Task Should_manage_app_properties() { - var appLabel = Guid.NewGuid().ToString(); - var appDescription = Guid.NewGuid().ToString(); + var newLabel = Guid.NewGuid().ToString(); + var newDescription = Guid.NewGuid().ToString(); // STEP 1: Update app - var updateRequest = new UpdateAppDto { Label = appLabel, Description = appDescription }; + var updateRequest = new UpdateAppDto { Label = newLabel, Description = newDescription }; - var app_1 = await _.Apps.UpdateAppAsync(_.AppName, updateRequest); + var app_1 = await _.Apps.PutAppAsync(_.AppName, updateRequest); - Assert.Equal(appLabel, app_1.Label); - Assert.Equal(appDescription, app_1.Description); + Assert.Equal(newLabel, app_1.Label); + Assert.Equal(newDescription, app_1.Description); } [Fact] @@ -252,40 +252,6 @@ namespace TestSuite.ApiTests Assert.DoesNotContain(roles_4.Items, x => x.Name == roleName); } - [Fact] - public async Task Should_manage_patterns() - { - var patternName = Guid.NewGuid().ToString(); - var patternRegex1 = Guid.NewGuid().ToString(); - var patternRegex2 = Guid.NewGuid().ToString(); - - // STEP 1: Add pattern. - var createRequest = new UpdatePatternDto { Name = patternName, Pattern = patternRegex1 }; - - var patterns_1 = await _.Apps.PostPatternAsync(_.AppName, createRequest); - var pattern_1 = patterns_1.Items.Single(x => x.Name == patternName); - - // Should return pattern with correct regex. - Assert.Equal(patternRegex1, pattern_1.Pattern); - - - // STEP 2: Update pattern. - var updateRequest = new UpdatePatternDto { Name = patternName, Pattern = patternRegex2 }; - - var patterns_2 = await _.Apps.PutPatternAsync(_.AppName, pattern_1.Id, updateRequest); - var pattern_2 = patterns_2.Items.Single(x => x.Name == patternName); - - // Should return pattern with correct regex. - Assert.Equal(patternRegex2, pattern_2.Pattern); - - - // STEP 3: Remove pattern. - var patterns_3 = await _.Apps.DeletePatternAsync(_.AppName, pattern_2.Id); - - // Should not return deleted pattern. - Assert.DoesNotContain(patterns_3.Items, x => x.Id == pattern_2.Id); - } - [Fact] public async Task Should_manage_languages() { @@ -365,5 +331,31 @@ namespace TestSuite.ApiTests Assert.Equal(new string[] { "it" }, languageDE_5.Fallback.ToArray()); Assert.Equal(new string[] { "it", "de", "en" }, languages_5.Items.Select(x => x.Iso2Code).ToArray()); } + + [Fact] + public async Task Should_manage_settings() + { + // STEP 1: Get initial settings. + var settings_0 = await _.Apps.GetAppSettingsAsync(_.AppName); + + Assert.NotEmpty(settings_0.Patterns); + Assert.Empty(settings_0.Editors); + + + // STEP 2: Update settings with new state. + var updateRequest = new UpdateAppSettingsDto + { + Patterns = settings_0.Patterns, + Editors = new List + { + new EditorDto { Name = "editor", Url = "http://squidex.io/path/to/editor" } + } + }; + + var settings_1 = await _.Apps.PutAppSettingsAsync(_.AppName, updateRequest); + + Assert.NotEmpty(settings_1.Patterns); + Assert.NotEmpty(settings_1.Editors); + } } } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj b/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj index 57f576e54..ca8e44e2e 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj +++ b/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj @@ -4,7 +4,7 @@ net5.0 - + diff --git a/backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj b/backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj index d73647f34..21761bd47 100644 --- a/backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj +++ b/backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj @@ -4,7 +4,7 @@ net5.0 - + diff --git a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj index b5cd20e54..fcf7f4cd4 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj +++ b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj @@ -5,13 +5,13 @@ TestSuite - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/frontend/app-config/webpack.config.js b/frontend/app-config/webpack.config.js index 62099a8ae..20ddf3378 100644 --- a/frontend/app-config/webpack.config.js +++ b/frontend/app-config/webpack.config.js @@ -6,7 +6,7 @@ function root() { var newArgs = Array.prototype.slice.call(arguments, 0); return path.join.apply(path, [appRoot].concat(newArgs)); -}; +} const plugins = { // https://github.com/webpack-contrib/mini-css-extract-plugin @@ -440,7 +440,10 @@ module.exports = function (env) { }, { loader: 'postcss-loader' }, { - loader: 'sass-loader?sourceMap' + loader: 'sass-loader', + options: { + sourceMap: true + } }], /* * Do not include component styles. diff --git a/frontend/app/app.component.ts b/frontend/app/app.component.ts index fa82cc4cd..ffcf70fed 100644 --- a/frontend/app/app.component.ts +++ b/frontend/app/app.component.ts @@ -13,5 +13,5 @@ import { Component } from '@angular/core'; templateUrl: './app.component.html' }) export class AppComponent { - public isLoaded = false; + public isLoaded?: boolean | null; } \ No newline at end of file diff --git a/frontend/app/features/administration/pages/cluster/cluster-page.component.html b/frontend/app/features/administration/pages/cluster/cluster-page.component.html index 52dd84476..727368f09 100644 --- a/frontend/app/features/administration/pages/cluster/cluster-page.component.html +++ b/frontend/app/features/administration/pages/cluster/cluster-page.component.html @@ -1,6 +1,6 @@ - +
diff --git a/frontend/app/features/administration/pages/event-consumers/event-consumer.component.html b/frontend/app/features/administration/pages/event-consumers/event-consumer.component.html index 726225fc7..3709ae139 100644 --- a/frontend/app/features/administration/pages/event-consumers/event-consumer.component.html +++ b/frontend/app/features/administration/pages/event-consumers/event-consumer.component.html @@ -1,4 +1,4 @@ - + diff --git a/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.html b/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.html index e1fc0bb36..288ffd334 100644 --- a/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.html +++ b/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.consumers' | sqxTranslate }} @@ -14,7 +14,7 @@ - + diff --git a/frontend/app/features/administration/pages/users/user-page.component.html b/frontend/app/features/administration/pages/users/user-page.component.html index 7a8d4283f..ab5e5abc9 100644 --- a/frontend/app/features/administration/pages/users/user-page.component.html +++ b/frontend/app/features/administration/pages/users/user-page.component.html @@ -2,7 +2,7 @@ - + diff --git a/frontend/app/features/administration/pages/users/user-page.component.ts b/frontend/app/features/administration/pages/users/user-page.component.ts index 9af96b925..f638fd444 100644 --- a/frontend/app/features/administration/pages/users/user-page.component.ts +++ b/frontend/app/features/administration/pages/users/user-page.component.ts @@ -17,7 +17,7 @@ import { ResourceOwner } from '@app/shared'; templateUrl: './user-page.component.html' }) export class UserPageComponent extends ResourceOwner implements OnInit { - public isEditable = true; + public isEditable = false; public user?: UserDto | null; public userForm = new UserForm(this.formBuilder); diff --git a/frontend/app/features/administration/pages/users/users-page.component.html b/frontend/app/features/administration/pages/users/users-page.component.html index 7cac6f278..f0dd57538 100644 --- a/frontend/app/features/administration/pages/users/users-page.component.html +++ b/frontend/app/features/administration/pages/users/users-page.component.html @@ -1,6 +1,6 @@ - + {{ 'users.listTitle' | sqxTranslate }} @@ -27,7 +27,7 @@ - +
diff --git a/frontend/app/features/administration/services/event-consumers.service.spec.ts b/frontend/app/features/administration/services/event-consumers.service.spec.ts index 3d7e2ec78..53fa2dbac 100644 --- a/frontend/app/features/administration/services/event-consumers.service.spec.ts +++ b/frontend/app/features/administration/services/event-consumers.service.spec.ts @@ -130,14 +130,16 @@ describe('EventConsumersService', () => { expect(eventConsumer!).toEqual(createEventConsumer(12)); })); - function eventConsumerResponse(id: number) { + function eventConsumerResponse(id: number, suffix = '') { + const key = `${id}${suffix}`; + return { name: `event-consumer${id}`, - position: `position${id}`, + position: `position${key}`, count: id, isStopped: true, isResetting: true, - error: `failure${id}`, + error: `failure${key}`, _links: { reset: { method: 'PUT', href: `/event-consumers/${id}/reset` } } @@ -150,11 +152,13 @@ export function createEventConsumer(id: number, suffix = '') { reset: { method: 'PUT', href: `/event-consumers/${id}/reset` } }; + const key = `${id}${suffix}`; + return new EventConsumerDto(links, `event-consumer${id}`, id, true, true, - `failure${id}${suffix}`, - `position${id}${suffix}`); + `failure${key}`, + `position${key}`); } \ No newline at end of file diff --git a/frontend/app/features/administration/services/users.service.spec.ts b/frontend/app/features/administration/services/users.service.spec.ts index 32a916e77..818baffc6 100644 --- a/frontend/app/features/administration/services/users.service.spec.ts +++ b/frontend/app/features/administration/services/users.service.spec.ts @@ -221,13 +221,15 @@ describe('UsersService', () => { req.flush({}); })); - function userResponse(id: number) { + function userResponse(id: number, suffix = '') { + const key = `${id}${suffix}`; + return { id: `${id}`, - email: `user${id}@domain.com`, - displayName: `user${id}`, + email: `user${key}@domain.com`, + displayName: `user${key}`, permissions: [ - `Permission${id}` + `Permission${key}` ], isLocked: true, _links: { @@ -244,12 +246,14 @@ export function createUser(id: number, suffix = '') { update: { method: 'PUT', href: `/users/${id}` } }; + const key = `${id}${suffix}`; + return new UserDto(links, `${id}`, - `user${id}${suffix}@domain.com`, - `user${id}${suffix}`, + `user${key}@domain.com`, + `user${key}`, [ - `Permission${id}${suffix}` + `Permission${key}` ], true); } \ No newline at end of file diff --git a/frontend/app/features/administration/services/users.service.ts b/frontend/app/features/administration/services/users.service.ts index f1f73ce37..0d91a5033 100644 --- a/frontend/app/features/administration/services/users.service.ts +++ b/frontend/app/features/administration/services/users.service.ts @@ -41,19 +41,13 @@ export class UserDto { } } -export interface CreateUserDto { - readonly email: string; - readonly displayName: string; - readonly permissions: ReadonlyArray; - readonly password: string; -} +type Permissions = readonly string[]; -export interface UpdateUserDto { - readonly email: string; - readonly displayName: string; - readonly permissions: ReadonlyArray; - readonly password?: string; -} +export type CreateUserDto = + Readonly<{ email: string, displayName: string, permissions: Permissions, password: string }>; + +export type UpdateUserDto = + Partial; @Injectable() export class UsersService { diff --git a/frontend/app/features/administration/state/users.state.ts b/frontend/app/features/administration/state/users.state.ts index 6041d457e..23764f32c 100644 --- a/frontend/app/features/administration/state/users.state.ts +++ b/frontend/app/features/administration/state/users.state.ts @@ -166,7 +166,7 @@ export class UsersState extends State { public delete(user: UserDto) { return this.usersService.deleteUser(user).pipe( - tap(updated => { + tap(() => { this.next(s => { const users = s.users.filter(x => x.id !== user.id); diff --git a/frontend/app/features/api/pages/graphql/graphql-page.component.html b/frontend/app/features/api/pages/graphql/graphql-page.component.html index 4329313be..f6ff6b925 100644 --- a/frontend/app/features/api/pages/graphql/graphql-page.component.html +++ b/frontend/app/features/api/pages/graphql/graphql-page.component.html @@ -1,5 +1,5 @@ - +
\ No newline at end of file diff --git a/frontend/app/features/apps/pages/onboarding-dialog.component.ts b/frontend/app/features/apps/pages/onboarding-dialog.component.ts index 897da9e80..9a7098202 100644 --- a/frontend/app/features/apps/pages/onboarding-dialog.component.ts +++ b/frontend/app/features/apps/pages/onboarding-dialog.component.ts @@ -23,6 +23,6 @@ export class OnboardingDialogComponent { public close = new EventEmitter(); public next() { - this.step = this.step + 1; + this.step += 1; } } \ No newline at end of file diff --git a/frontend/app/features/assets/pages/assets-filters-page.component.html b/frontend/app/features/assets/pages/assets-filters-page.component.html index 730c9cb1e..e954b72a4 100644 --- a/frontend/app/features/assets/pages/assets-filters-page.component.html +++ b/frontend/app/features/assets/pages/assets-filters-page.component.html @@ -1,4 +1,4 @@ - + {{ 'common.filters' | sqxTranslate }} @@ -7,8 +7,8 @@

{{ 'common.tags' | sqxTranslate }}

diff --git a/frontend/app/features/assets/pages/assets-page.component.html b/frontend/app/features/assets/pages/assets-page.component.html index a1f7ddb42..70cf0e3db 100644 --- a/frontend/app/features/assets/pages/assets-page.component.html +++ b/frontend/app/features/assets/pages/assets-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.assets' | sqxTranslate }} @@ -17,7 +17,7 @@
diff --git a/frontend/app/features/content/pages/comments/comments-page.component.html b/frontend/app/features/content/pages/comments/comments-page.component.html index d3d658289..c8985e6d0 100644 --- a/frontend/app/features/content/pages/comments/comments-page.component.html +++ b/frontend/app/features/content/pages/comments/comments-page.component.html @@ -1,4 +1,4 @@ - + {{ 'comments.title' | sqxTranslate }} diff --git a/frontend/app/features/content/pages/content/content-history-page.component.html b/frontend/app/features/content/pages/content/content-history-page.component.html index 1c0692878..82ea48fb8 100644 --- a/frontend/app/features/content/pages/content/content-history-page.component.html +++ b/frontend/app/features/content/pages/content/content-history-page.component.html @@ -1,4 +1,4 @@ - + {{ 'common.workflow' | sqxTranslate }} @@ -32,8 +32,8 @@ @@ -75,7 +75,7 @@
- + {{ 'contents.lastUpdatedLabel' | sqxTranslate }}: {{content.lastModified | sqxFromNow}}
-

{{ 'common.history' | sqxTranslate }}

+

{{ 'common.history2' | sqxTranslate }}

- \ No newline at end of file + \ No newline at end of file diff --git a/frontend/app/features/content/pages/content/content-history-page.component.ts b/frontend/app/features/content/pages/content/content-history-page.component.ts index 2ac4228ee..f42708ee3 100644 --- a/frontend/app/features/content/pages/content/content-history-page.component.ts +++ b/frontend/app/features/content/pages/content/content-history-page.component.ts @@ -32,6 +32,10 @@ export class ContentHistoryPageComponent extends ResourceOwner implements OnInit public dropdown = new ModalModel(); public dropdownNew = new ModalModel(); + public get disableScheduler() { + return this.appsState.snapshot.selectedSettings?.hideScheduler === true; + } + constructor( private readonly appsState: AppsState, private readonly contentPage: ContentPageComponent, diff --git a/frontend/app/features/content/pages/content/content-page.component.html b/frontend/app/features/content/pages/content/content-page.component.html index d211fc8c5..445ece6e5 100644 --- a/frontend/app/features/content/pages/content/content-page.component.html +++ b/frontend/app/features/content/pages/content/content-page.component.html @@ -1,7 +1,7 @@ - + @@ -102,7 +102,7 @@ - + @@ -164,7 +164,7 @@ - + {{ 'common.sidebarTour' | sqxTranslate }}
diff --git a/frontend/app/features/content/pages/content/content-page.component.ts b/frontend/app/features/content/pages/content/content-page.component.ts index 3cf3809fc..cfde8ae58 100644 --- a/frontend/app/features/content/pages/content/content-page.component.ts +++ b/frontend/app/features/content/pages/content/content-page.component.ts @@ -282,6 +282,6 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD } } -function isOtherContent(lhs: ContentDto | null | undefined, rhs: ContentDto | null | undefined) { +function isOtherContent(lhs: ContentDto | undefined | null, rhs: ContentDto | undefined | null) { return !lhs || !rhs || lhs.id !== rhs.id; } \ No newline at end of file diff --git a/frontend/app/features/content/pages/content/editor/content-editor.component.ts b/frontend/app/features/content/pages/content/editor/content-editor.component.ts index fa2705eb6c..4b71b4630 100644 --- a/frontend/app/features/content/pages/content/editor/content-editor.component.ts +++ b/frontend/app/features/content/pages/content/editor/content-editor.component.ts @@ -27,7 +27,7 @@ export class ContentEditorComponent { public contentVersion: Version | null; @Input() - public contentFormCompare?: EditContentForm; + public contentFormCompare?: EditContentForm | null; @Input() public schema: SchemaDetailsDto; diff --git a/frontend/app/features/content/pages/content/editor/content-field.component.html b/frontend/app/features/content/pages/content/editor/content-field.component.html index 20f42a41f..eed2e1281 100644 --- a/frontend/app/features/content/pages/content/editor/content-field.component.html +++ b/frontend/app/features/content/pages/content/editor/content-field.component.html @@ -79,7 +79,7 @@ diff --git a/frontend/app/features/content/pages/content/editor/content-field.component.ts b/frontend/app/features/content/pages/content/editor/content-field.component.ts index 5efd6f2b5..75eebd2b8 100644 --- a/frontend/app/features/content/pages/content/editor/content-field.component.ts +++ b/frontend/app/features/content/pages/content/editor/content-field.component.ts @@ -20,13 +20,13 @@ export class ContentFieldComponent implements OnChanges { public languageChange = new EventEmitter(); @Input() - public isCompact = false; + public isCompact?: boolean | null; @Input() public form: EditContentForm; @Input() - public formCompare?: EditContentForm; + public formCompare?: EditContentForm | null; @Input() public formContext: any; diff --git a/frontend/app/features/content/pages/content/editor/content-section.component.html b/frontend/app/features/content/pages/content/editor/content-section.component.html index 1ee96da7b..cdbc44d29 100644 --- a/frontend/app/features/content/pages/content/editor/content-section.component.html +++ b/frontend/app/features/content/pages/content/editor/content-section.component.html @@ -1,4 +1,4 @@ - +
@@ -7,10 +7,10 @@
-

{{separator!.displayName}}

+

{{separator.displayName}}

- - {{separator!.properties.hints}} + + {{separator.properties.hints}}
diff --git a/frontend/app/features/content/pages/content/editor/content-section.component.ts b/frontend/app/features/content/pages/content/editor/content-section.component.ts index d115f6e0c..840e64184 100644 --- a/frontend/app/features/content/pages/content/editor/content-section.component.ts +++ b/frontend/app/features/content/pages/content/editor/content-section.component.ts @@ -24,13 +24,13 @@ export class ContentSectionComponent extends StatefulComponent implements public languageChange = new EventEmitter(); @Input() - public isCompact = false; + public isCompact?: boolean | null; @Input() public form: EditContentForm; @Input() - public formCompare?: EditContentForm; + public formCompare?: EditContentForm | null; @Input() public formContext: any; diff --git a/frontend/app/features/content/pages/content/editor/field-languages.component.html b/frontend/app/features/content/pages/content/editor/field-languages.component.html index 24533aba2..75b12b92b 100644 --- a/frontend/app/features/content/pages/content/editor/field-languages.component.html +++ b/frontend/app/features/content/pages/content/editor/field-languages.component.html @@ -18,7 +18,7 @@
- + {{ 'contents.validationHint' | sqxTranslate }}
diff --git a/frontend/app/features/content/pages/content/editor/field-languages.component.ts b/frontend/app/features/content/pages/content/editor/field-languages.component.ts index a4d843a49..dc9f80a4c 100644 --- a/frontend/app/features/content/pages/content/editor/field-languages.component.ts +++ b/frontend/app/features/content/pages/content/editor/field-languages.component.ts @@ -19,7 +19,7 @@ export class FieldLanguagesComponent { public showAllControlsChange = new EventEmitter(); @Input() - public showAllControls: boolean; + public showAllControls?: boolean | null; @Output() public languageChange = new EventEmitter(); diff --git a/frontend/app/features/content/pages/content/references/content-references.component.html b/frontend/app/features/content/pages/content/references/content-references.component.html index 4c3843eed..50ceebd8d 100644 --- a/frontend/app/features/content/pages/content/references/content-references.component.html +++ b/frontend/app/features/content/pages/content/references/content-references.component.html @@ -1,4 +1,4 @@ - +
diff --git a/frontend/app/features/content/pages/contents/contents-filters-page.component.html b/frontend/app/features/content/pages/contents/contents-filters-page.component.html index a7c2bae4f..055954f93 100644 --- a/frontend/app/features/content/pages/contents/contents-filters-page.component.html +++ b/frontend/app/features/content/pages/contents/contents-filters-page.component.html @@ -1,4 +1,4 @@ - + {{ 'common.filters' | sqxTranslate }} diff --git a/frontend/app/features/content/pages/contents/contents-page.component.html b/frontend/app/features/content/pages/contents/contents-page.component.html index 17eda8344..6dff86c17 100644 --- a/frontend/app/features/content/pages/contents/contents-page.component.html +++ b/frontend/app/features/content/pages/contents/contents-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.contents' | sqxTranslate }} @@ -23,8 +23,8 @@ [queries]="queries | async" [queriesTypes]="'common.contents' | sqxTranslate" [queryModel]="queryModel | async" - [language]="languagesState.isoMasterLanguage | async" - enableShortcut="true"> + [language]="(languagesState.isoMasterLanguage | async)!" + [enableShortcut]="true">
@@ -42,7 +42,7 @@ - +
{{ 'contents.selectionCount' | sqxTranslate: { count: selectionCount } }}   @@ -72,7 +72,7 @@ @@ -99,7 +99,7 @@ @@ -149,4 +149,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/app/features/content/pages/contents/contents-page.component.ts b/frontend/app/features/content/pages/contents/contents-page.component.ts index 8495b2025..6175e984c 100644 --- a/frontend/app/features/content/pages/contents/contents-page.component.ts +++ b/frontend/app/features/content/pages/contents/contents-page.component.ts @@ -45,6 +45,10 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit { public language: AppLanguageDto; public languages: ReadonlyArray; + public get disableScheduler() { + return this.appsState.snapshot.selectedSettings?.hideScheduler === true; + } + public queryModel = combineLatest([ this.schemasState.selectedSchema.pipe(defined()), diff --git a/frontend/app/features/content/pages/contents/custom-view-editor.component.ts b/frontend/app/features/content/pages/contents/custom-view-editor.component.ts index a21df2f08..74d16bbd5 100644 --- a/frontend/app/features/content/pages/contents/custom-view-editor.component.ts +++ b/frontend/app/features/content/pages/contents/custom-view-editor.component.ts @@ -21,7 +21,7 @@ export class CustomViewEditorComponent implements OnChanges { public fieldNamesChange = new EventEmitter>(); @Input() - public fieldNames: ReadonlyArray; + public fieldNames: string[]; @Input() public allFields: ReadonlyArray; @@ -32,7 +32,7 @@ export class CustomViewEditorComponent implements OnChanges { this.fieldsNotAdded = this.allFields.filter(n => this.fieldNames.indexOf(n) < 0); } - public drop(event: CdkDragDrop) { + public drop(event: CdkDragDrop) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); this.updateFieldNames(event.container.data); diff --git a/frontend/app/features/content/pages/schemas/schemas-page.component.html b/frontend/app/features/content/pages/schemas/schemas-page.component.html index 1133d2e2b..9eae70509 100644 --- a/frontend/app/features/content/pages/schemas/schemas-page.component.html +++ b/frontend/app/features/content/pages/schemas/schemas-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.schemas' | sqxTranslate }} diff --git a/frontend/app/features/content/pages/sidebar/sidebar-page.component.html b/frontend/app/features/content/pages/sidebar/sidebar-page.component.html index fb578f387..7ad11016f 100644 --- a/frontend/app/features/content/pages/sidebar/sidebar-page.component.html +++ b/frontend/app/features/content/pages/sidebar/sidebar-page.component.html @@ -1,4 +1,4 @@ - + {{ 'common.sidebar' | sqxTranslate }} @@ -7,7 +7,7 @@ + [contentSchema]="(schemasState.selectedSchema | async)!"> diff --git a/frontend/app/features/content/shared/content-extension.component.ts b/frontend/app/features/content/shared/content-extension.component.ts index f319d4779..c20b9971b 100644 --- a/frontend/app/features/content/shared/content-extension.component.ts +++ b/frontend/app/features/content/shared/content-extension.component.ts @@ -8,7 +8,7 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, Renderer2, SimpleChanges, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; import { ApiUrlConfig, ResourceOwner, Types } from '@app/framework/internal'; -import { AppsState, AuthService, ContentDto, SchemaDto } from '@app/shared'; +import { AppsState, AuthService, computeEditorUrl, ContentDto, SchemaDto } from '@app/shared'; @Component({ selector: 'sqx-content-extension', @@ -18,13 +18,14 @@ import { AppsState, AuthService, ContentDto, SchemaDto } from '@app/shared'; }) export class ContentExtensionComponent extends ResourceOwner implements AfterViewInit, OnChanges { private readonly context: any; + private computedUrl: string; private isInitialized = false; @Input() - public url: string; + public url?: string | null; @Input() - public content: ContentDto; + public content?: ContentDto | null; @Input() public contentSchema: SchemaDto; @@ -32,7 +33,8 @@ export class ContentExtensionComponent extends ResourceOwner implements AfterVie @ViewChild('iframe', { static: false }) public iframe: ElementRef; - constructor(apiUrl: ApiUrlConfig, authService: AuthService, appsState: AppsState, + constructor(apiUrl: ApiUrlConfig, authService: AuthService, + private readonly appsState: AppsState, private readonly renderer: Renderer2, private readonly router: Router ) { @@ -47,8 +49,10 @@ export class ContentExtensionComponent extends ResourceOwner implements AfterVie } public ngOnChanges(changes: SimpleChanges) { - if (changes['url'] && this.iframe?.nativeElement) { - this.iframe.nativeElement.src = this.url || ''; + if (changes['url']) { + this.computedUrl = computeEditorUrl(this.url, this.appsState.snapshot.selectedSettings); + + this.setupUrl(); } if (changes['contentSchema']) { @@ -61,8 +65,14 @@ export class ContentExtensionComponent extends ResourceOwner implements AfterVie } } + private setupUrl() { + if (this.iframe?.nativeElement) { + this.iframe.nativeElement.src = this.computedUrl; + } + } + public ngAfterViewInit() { - this.iframe.nativeElement.src = this.url || ''; + this.setupUrl(); this.own( this.renderer.listen('window', 'message', (event: MessageEvent) => { diff --git a/frontend/app/features/content/shared/content-status.component.ts b/frontend/app/features/content/shared/content-status.component.ts index 9bdb4dd53..5157a39bc 100644 --- a/frontend/app/features/content/shared/content-status.component.ts +++ b/frontend/app/features/content/shared/content-status.component.ts @@ -22,16 +22,16 @@ export class ContentStatusComponent { public statusColor: string; @Input() - public scheduled?: ScheduleDto; + public scheduled?: ScheduleDto | null; @Input() public layout: 'icon' | 'text' | 'multiline' = 'icon'; @Input() - public truncate = false; + public truncate?: boolean | null; @Input() - public small = false; + public small?: boolean | null; public get isMultiline() { return this.layout === 'multiline'; diff --git a/frontend/app/features/content/shared/due-time-selector.component.html b/frontend/app/features/content/shared/due-time-selector.component.html index c23ab328c..21097ef93 100644 --- a/frontend/app/features/content/shared/due-time-selector.component.html +++ b/frontend/app/features/content/shared/due-time-selector.component.html @@ -19,7 +19,7 @@
- +
diff --git a/frontend/app/features/content/shared/due-time-selector.component.ts b/frontend/app/features/content/shared/due-time-selector.component.ts index 0aaeac08a..187028973 100644 --- a/frontend/app/features/content/shared/due-time-selector.component.ts +++ b/frontend/app/features/content/shared/due-time-selector.component.ts @@ -5,8 +5,8 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component } from '@angular/core'; -import { DialogModel, UIOptions } from '@app/shared'; +import { Component, Input } from '@angular/core'; +import { DialogModel } from '@app/shared'; import { Observable, of, Subject } from 'rxjs'; const OPTION_IMMEDIATELY = 'Immediately'; @@ -17,18 +17,16 @@ const OPTION_IMMEDIATELY = 'Immediately'; templateUrl: './due-time-selector.component.html' }) export class DueTimeSelectorComponent { - private readonly disabled: boolean; private dueTimeResult: Subject; + @Input() + public disabled?: boolean | null; + public dueTimeDialog = new DialogModel(); public dueTime: string | null = ''; public dueTimeAction: string | null = ''; public dueTimeMode = OPTION_IMMEDIATELY; - constructor(uiOptions: UIOptions) { - this.disabled = uiOptions.get('disableScheduledChanges') === true; - } - public selectDueTime(action: string): Observable { if (this.disabled) { return of(null); diff --git a/frontend/app/features/content/shared/forms/array-editor.component.ts b/frontend/app/features/content/shared/forms/array-editor.component.ts index eac56c6df..38dd64573 100644 --- a/frontend/app/features/content/shared/forms/array-editor.component.ts +++ b/frontend/app/features/content/shared/forms/array-editor.component.ts @@ -29,7 +29,7 @@ export class ArrayEditorComponent implements OnChanges { public formModel: FieldArrayForm; @Input() - public canUnset: boolean; + public canUnset?: boolean | null; @Input() public language: AppLanguageDto; diff --git a/frontend/app/features/content/shared/forms/array-item.component.html b/frontend/app/features/content/shared/forms/array-item.component.html index afa9ada76..2865366ee 100644 --- a/frontend/app/features/content/shared/forms/array-item.component.html +++ b/frontend/app/features/content/shared/forms/array-item.component.html @@ -48,7 +48,7 @@ [canUnset]="canUnset" [form]="form" [formContext]="formContext" - [formSection]="section" + [formSection]="$any(section)" [index]="index" [language]="language" [languages]="languages"> diff --git a/frontend/app/features/content/shared/forms/array-item.component.ts b/frontend/app/features/content/shared/forms/array-item.component.ts index 96f6eb21e..927c88c16 100644 --- a/frontend/app/features/content/shared/forms/array-item.component.ts +++ b/frontend/app/features/content/shared/forms/array-item.component.ts @@ -42,16 +42,16 @@ export class ArrayItemComponent extends StatefulComponent implements OnCh public formModel: FieldArrayItemForm; @Input() - public canUnset: boolean; + public canUnset?: boolean | null; @Input() - public isFirst = false; + public isFirst?: boolean | null; @Input() - public isLast = false; + public isLast?: boolean | null; @Input() - public isDisabled = false; + public isDisabled?: boolean | null; @Input() public index: number; diff --git a/frontend/app/features/content/shared/forms/array-section.component.html b/frontend/app/features/content/shared/forms/array-section.component.html index 2723c4fde..db62399eb 100644 --- a/frontend/app/features/content/shared/forms/array-section.component.html +++ b/frontend/app/features/content/shared/forms/array-section.component.html @@ -2,7 +2,7 @@

{{separator!.displayName}}

- + {{separator!.properties.hints}}
diff --git a/frontend/app/features/content/shared/forms/array-section.component.ts b/frontend/app/features/content/shared/forms/array-section.component.ts index f1812d123..1d4de951a 100644 --- a/frontend/app/features/content/shared/forms/array-section.component.ts +++ b/frontend/app/features/content/shared/forms/array-section.component.ts @@ -35,7 +35,7 @@ export class ArraySectionComponent { public index: number; @Input() - public canUnset: boolean; + public canUnset?: boolean | null; @ViewChildren(FieldEditorComponent) public editors: QueryList; diff --git a/frontend/app/features/content/shared/forms/assets-editor.component.html b/frontend/app/features/content/shared/forms/assets-editor.component.html index 0303d2925..5af162d6b 100644 --- a/frontend/app/features/content/shared/forms/assets-editor.component.html +++ b/frontend/app/features/content/shared/forms/assets-editor.component.html @@ -1,8 +1,8 @@ -
+
-
+
{{ 'contents.assetsUpload' | sqxTranslate }}
diff --git a/frontend/app/features/content/shared/forms/field-editor.component.html b/frontend/app/features/content/shared/forms/field-editor.component.html index 12ed71699..bd2431dbe 100644 --- a/frontend/app/features/content/shared/forms/field-editor.component.html +++ b/frontend/app/features/content/shared/forms/field-editor.component.html @@ -24,7 +24,7 @@ @@ -47,13 +47,13 @@ - + - + @@ -129,7 +129,7 @@ - + @@ -188,7 +188,7 @@
- +
diff --git a/frontend/app/features/content/shared/forms/field-editor.component.ts b/frontend/app/features/content/shared/forms/field-editor.component.ts index 0040dd587..3e65f8281 100644 --- a/frontend/app/features/content/shared/forms/field-editor.component.ts +++ b/frontend/app/features/content/shared/forms/field-editor.component.ts @@ -6,7 +6,7 @@ */ import { Component, ElementRef, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; -import { AbstractControl, FormArray, FormControl } from '@angular/forms'; +import { AbstractControl, FormControl } from '@angular/forms'; import { AbstractContentForm, AppLanguageDto, EditContentForm, FieldDto, MathHelper, RootFieldDto, Types } from '@app/shared'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -36,7 +36,7 @@ export class FieldEditorComponent implements OnChanges { public index: number; @Input() - public canUnset: boolean; + public canUnset?: boolean | null; @Input() public displaySuffix: string; @@ -50,10 +50,6 @@ export class FieldEditorComponent implements OnChanges { return this.formModel.field; } - public get arrayControl() { - return this.formModel.form as FormArray; - } - public get editorControl() { return this.formModel.form as FormControl; } diff --git a/frontend/app/features/content/shared/forms/iframe-editor.component.ts b/frontend/app/features/content/shared/forms/iframe-editor.component.ts index f113aad92..688741d30 100644 --- a/frontend/app/features/content/shared/forms/iframe-editor.component.ts +++ b/frontend/app/features/content/shared/forms/iframe-editor.component.ts @@ -8,8 +8,8 @@ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnChanges, OnDestroy, Renderer2, SimpleChanges, ViewChild } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { Router } from '@angular/router'; -import { DialogModel, DialogService, StatefulControlComponent, Types } from '@app/framework/internal'; -import { AssetDto } from '@app/shared'; +import { DialogModel, DialogService, StatefulControlComponent, Types } from '@app/framework'; +import { AppsState, AssetDto, computeEditorUrl } from '@app/shared'; export const SQX_IFRAME_EDITOR_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => IFrameEditorComponent), multi: true @@ -32,6 +32,7 @@ interface State { export class IFrameEditorComponent extends StatefulControlComponent implements OnChanges, OnDestroy, AfterViewInit { private value: any; private isInitialized = false; + private computedUrl: string; private assetsCorrelationId: any; @ViewChild('iframe', { static: false }) @@ -53,7 +54,7 @@ export class IFrameEditorComponent extends StatefulControlComponent public formIndex?: number | null; @Input() - public language: string; + public language?: string | null; @Input() public url: string; @@ -63,6 +64,7 @@ export class IFrameEditorComponent extends StatefulControlComponent public fullscreen: boolean; constructor(changeDetector: ChangeDetectorRef, + private readonly appsState: AppsState, private readonly dialogs: DialogService, private readonly renderer: Renderer2, private readonly router: Router @@ -77,11 +79,13 @@ export class IFrameEditorComponent extends StatefulControlComponent } public ngOnChanges(changes: SimpleChanges) { - if (this.iframe) { - if (changes['url']) { - this.setupUrl(); - } + if (changes['url']) { + this.computedUrl = computeEditorUrl(this.url, this.appsState.snapshot.selectedSettings); + + this.setupUrl(); + } + if (this.iframe?.nativeElement) { if (changes['formValue']) { this.sendFormValue(); } @@ -97,7 +101,9 @@ export class IFrameEditorComponent extends StatefulControlComponent } private setupUrl() { - this.iframe.nativeElement.src = this.url; + if (this.iframe?.nativeElement) { + this.iframe.nativeElement.src = this.computedUrl; + } } public ngAfterViewInit() { @@ -251,7 +257,7 @@ export class IFrameEditorComponent extends StatefulControlComponent } private sendMessage(type: string, payload: any) { - if (!this.iframe) { + if (!this.iframe?.nativeElement) { return; } diff --git a/frontend/app/features/content/shared/forms/stock-photo-editor.component.html b/frontend/app/features/content/shared/forms/stock-photo-editor.component.html index 2a90bc20b..158d0c82d 100644 --- a/frontend/app/features/content/shared/forms/stock-photo-editor.component.html +++ b/frontend/app/features/content/shared/forms/stock-photo-editor.component.html @@ -22,10 +22,10 @@ - +
- +
diff --git a/frontend/app/features/content/shared/list/content-list-field.component.html b/frontend/app/features/content/shared/list/content-list-field.component.html index 91e675751..414aec1cf 100644 --- a/frontend/app/features/content/shared/list/content-list-field.component.html +++ b/frontend/app/features/content/shared/list/content-list-field.component.html @@ -24,7 +24,7 @@
- @@ -34,10 +34,10 @@
-
@@ -45,7 +45,7 @@ - + [status]="content.scheduleJob.status" + [statusColor]="content.scheduleJob.color"> {{ 'contents.scheduledAtLabel' | sqxTranslate }} {{content.scheduleJob?.dueTime | sqxShortDate}} @@ -76,7 +76,7 @@ @@ -94,8 +94,8 @@ {{content.version.value}} - - + + diff --git a/frontend/app/features/content/shared/list/content-list-field.component.ts b/frontend/app/features/content/shared/list/content-list-field.component.ts index a52b0813d..1051fa4ef 100644 --- a/frontend/app/features/content/shared/list/content-list-field.component.ts +++ b/frontend/app/features/content/shared/list/content-list-field.component.ts @@ -28,10 +28,10 @@ export class ContentListFieldComponent extends StatefulComponent implemen public content: ContentDto; @Input() - public patchAllowed: boolean; + public patchAllowed?: boolean | null; @Input() - public patchForm: FormGroup; + public patchForm?: FormGroup | null; @Input() public language: LanguageDto; diff --git a/frontend/app/features/content/shared/list/content.component.html b/frontend/app/features/content/shared/list/content.component.html index b728114bf..ea9fb47d6 100644 --- a/frontend/app/features/content/shared/list/content.component.html +++ b/frontend/app/features/content/shared/list/content.component.html @@ -31,7 +31,7 @@ {{ 'common.statusChangeTo' | sqxTranslate }} - diff --git a/frontend/app/features/content/shared/list/content.component.ts b/frontend/app/features/content/shared/list/content.component.ts index f1b0eb2f8..6f30afc38 100644 --- a/frontend/app/features/content/shared/list/content.component.ts +++ b/frontend/app/features/content/shared/list/content.component.ts @@ -43,7 +43,7 @@ export class ContentComponent implements OnChanges { public listFields: ReadonlyArray; @Input() - public cloneable: boolean; + public cloneable?: boolean | null; @Input() public link: any = null; @@ -55,7 +55,7 @@ export class ContentComponent implements OnChanges { public fields: QueryList; public patchForm: PatchContentForm; - public patchAllowed = false; + public patchAllowed?: boolean | null; public dropdown = new ModalModel(); diff --git a/frontend/app/features/content/shared/references/content-creator.component.html b/frontend/app/features/content/shared/references/content-creator.component.html index 7c1fd5891..67279c44d 100644 --- a/frontend/app/features/content/shared/references/content-creator.component.html +++ b/frontend/app/features/content/shared/references/content-creator.component.html @@ -15,7 +15,7 @@
- +
@@ -28,7 +28,7 @@ {{ 'contents.referencesCreatePublish' | sqxTranslate }} - +
diff --git a/frontend/app/features/content/shared/references/content-creator.component.ts b/frontend/app/features/content/shared/references/content-creator.component.ts index a7d0a2344..8a2226820 100644 --- a/frontend/app/features/content/shared/references/content-creator.component.ts +++ b/frontend/app/features/content/shared/references/content-creator.component.ts @@ -6,7 +6,7 @@ */ import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { AppLanguageDto, ComponentContentsState, ContentDto, EditContentForm, LanguageDto, ResourceOwner, SchemaDetailsDto, SchemaDto, SchemasState, Types } from '@app/shared'; +import { AppLanguageDto, ComponentContentsState, ContentDto, EditContentForm, ResourceOwner, SchemaDetailsDto, SchemaDto, SchemasState, Types } from '@app/shared'; @Component({ selector: 'sqx-content-creator', @@ -24,7 +24,7 @@ export class ContentCreatorComponent extends ResourceOwner implements OnInit { public schemaIds: ReadonlyArray; @Input() - public language: LanguageDto; + public language: AppLanguageDto; @Input() public languages: ReadonlyArray; diff --git a/frontend/app/features/content/shared/references/content-selector-item.component.ts b/frontend/app/features/content/shared/references/content-selector-item.component.ts index abbb06f67..98a61e68e 100644 --- a/frontend/app/features/content/shared/references/content-selector-item.component.ts +++ b/frontend/app/features/content/shared/references/content-selector-item.component.ts @@ -21,10 +21,10 @@ export class ContentSelectorItemComponent { public selectedChange = new EventEmitter(); @Input() - public selected = false; + public selected?: boolean | null; @Input() - public selectable = true; + public selectable?: boolean | null = true; @Input() public language: LanguageDto; diff --git a/frontend/app/features/content/shared/references/content-selector.component.html b/frontend/app/features/content/shared/references/content-selector.component.html index e5835cfc0..8479382aa 100644 --- a/frontend/app/features/content/shared/references/content-selector.component.html +++ b/frontend/app/features/content/shared/references/content-selector.component.html @@ -1,4 +1,4 @@ - +
@@ -36,7 +36,7 @@ - +
@@ -52,7 +52,7 @@
diff --git a/frontend/app/features/content/shared/references/content-selector.component.ts b/frontend/app/features/content/shared/references/content-selector.component.ts index a3ee10382..026b68745 100644 --- a/frontend/app/features/content/shared/references/content-selector.component.ts +++ b/frontend/app/features/content/shared/references/content-selector.component.ts @@ -30,7 +30,7 @@ export class ContentSelectorComponent extends ResourceOwner implements OnInit { public languages: ReadonlyArray; @Input() - public allowDuplicates: boolean; + public allowDuplicates?: boolean | null; @Input() public alreadySelected: ReadonlyArray; diff --git a/frontend/app/features/content/shared/references/reference-item.component.ts b/frontend/app/features/content/shared/references/reference-item.component.ts index d2937075a..e7193d71d 100644 --- a/frontend/app/features/content/shared/references/reference-item.component.ts +++ b/frontend/app/features/content/shared/references/reference-item.component.ts @@ -24,19 +24,19 @@ export class ReferenceItemComponent implements OnChanges { public language: AppLanguageDto; @Input() - public canRemove = true; + public canRemove?: boolean | null = true; @Input() - public isCompact = false; + public isCompact?: boolean | null; @Input() - public isDisabled = false; + public isDisabled?: boolean | null; @Input() public validations: { [id: string]: boolean }; @Input() - public validityVisible: boolean; + public validityVisible?: boolean | null; @Input() public columns = 0; diff --git a/frontend/app/features/content/shared/references/references-editor.component.ts b/frontend/app/features/content/shared/references/references-editor.component.ts index 167e20a9e..165c251c7 100644 --- a/frontend/app/features/content/shared/references/references-editor.component.ts +++ b/frontend/app/features/content/shared/references/references-editor.component.ts @@ -45,7 +45,7 @@ export class ReferencesEditorComponent extends StatefulControlComponent(); diff --git a/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.ts b/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.ts index a71e57ccf..1b079e860 100644 --- a/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.ts +++ b/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.ts @@ -27,7 +27,7 @@ export class ApiTrafficCardComponent implements OnChanges { public usage: CallsUsageDto; @Input() - public isStacked = false; + public isStacked?: boolean | null; @Output() public isStackedChange = new EventEmitter(); diff --git a/frontend/app/features/dashboard/pages/dashboard-config.component.html b/frontend/app/features/dashboard/pages/dashboard-config.component.html index 26db96e1d..209bc2e64 100644 --- a/frontend/app/features/dashboard/pages/dashboard-config.component.html +++ b/frontend/app/features/dashboard/pages/dashboard-config.component.html @@ -35,7 +35,7 @@ - + {{ 'dashboard.editConfig' | sqxTranslate }} diff --git a/frontend/app/features/dashboard/pages/dashboard-config.component.ts b/frontend/app/features/dashboard/pages/dashboard-config.component.ts index f460ffe76..1ef88a613 100644 --- a/frontend/app/features/dashboard/pages/dashboard-config.component.ts +++ b/frontend/app/features/dashboard/pages/dashboard-config.component.ts @@ -22,13 +22,13 @@ import { take } from 'rxjs/operators'; }) export class DashboardConfigComponent implements OnChanges { @Input() - public app: AppDto[]; + public app: AppDto; @Input() public config: GridsterItem[]; @Input() - public needsAttention = false; + public needsAttention?: boolean | null; @Output() public configChange = new EventEmitter(); diff --git a/frontend/app/features/dashboard/pages/dashboard-page.component.html b/frontend/app/features/dashboard/pages/dashboard-page.component.html index b02072164..2a8d6ca28 100644 --- a/frontend/app/features/dashboard/pages/dashboard-page.component.html +++ b/frontend/app/features/dashboard/pages/dashboard-page.component.html @@ -60,8 +60,8 @@ - - + + diff --git a/frontend/app/features/dashboard/pages/dashboard-page.component.scss b/frontend/app/features/dashboard/pages/dashboard-page.component.scss index c185cff47..98d79a0de 100644 --- a/frontend/app/features/dashboard/pages/dashboard-page.component.scss +++ b/frontend/app/features/dashboard/pages/dashboard-page.component.scss @@ -85,7 +85,7 @@ gridster-item { &-title { color: $color-title; font-size: 1.2rem; - font-weight: light; + font-weight: lighter; margin-top: 1rem; } diff --git a/frontend/app/features/rules/pages/events/rule-events-page.component.html b/frontend/app/features/rules/pages/events/rule-events-page.component.html index 0a0f96f29..120caacd1 100644 --- a/frontend/app/features/rules/pages/events/rule-events-page.component.html +++ b/frontend/app/features/rules/pages/events/rule-events-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.events' | sqxTranslate }} diff --git a/frontend/app/features/rules/pages/rules/rule-element.component.ts b/frontend/app/features/rules/pages/rules/rule-element.component.ts index 21d190256..00826c2ec 100644 --- a/frontend/app/features/rules/pages/rules/rule-element.component.ts +++ b/frontend/app/features/rules/pages/rules/rule-element.component.ts @@ -22,8 +22,8 @@ export class RuleElementComponent { public element: RuleElementDto; @Input() - public isSmall = true; + public isSmall?: boolean | null = true; @Input() - public disabled = false; + public disabled?: boolean | null; } \ No newline at end of file diff --git a/frontend/app/features/rules/pages/rules/rule-wizard.component.html b/frontend/app/features/rules/pages/rules/rule-wizard.component.html index 523dc3991..5da2109bd 100644 --- a/frontend/app/features/rules/pages/rules/rule-wizard.component.html +++ b/frontend/app/features/rules/pages/rules/rule-wizard.component.html @@ -1,4 +1,4 @@ - + {{ 'rules.triggerEdit' | sqxTranslate }} diff --git a/frontend/app/features/rules/pages/rules/rule-wizard.component.ts b/frontend/app/features/rules/pages/rules/rule-wizard.component.ts index 138bd6826..c2a40e229 100644 --- a/frontend/app/features/rules/pages/rules/rule-wizard.component.ts +++ b/frontend/app/features/rules/pages/rules/rule-wizard.component.ts @@ -32,7 +32,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { public schemas: ReadonlyArray; @Input() - public rule: RuleDto; + public rule?: RuleDto | null; @Input() public mode = MODE_WIZARD; @@ -55,7 +55,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { return this.ruleTriggers[this.trigger.triggerType]; } - public isEditable: boolean; + public isEditable = false; public step = 1; @@ -70,11 +70,11 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { if (this.mode === MODE_EDIT_ACTION) { this.step = 4; - this.action = this.rule.action; + this.action = this.rule?.action; } else if (this.mode === MODE_EDIT_TRIGGER) { this.step = 2; - this.trigger = this.rule.trigger; + this.trigger = this.rule?.trigger; } } @@ -152,7 +152,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { } private updateTrigger() { - if (!this.isEditable) { + if (!this.isEditable || !this.rule) { return; } @@ -167,7 +167,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { } private updateAction() { - if (!this.isEditable) { + if (!this.isEditable || !this.rule) { return; } diff --git a/frontend/app/features/rules/pages/rules/rules-page.component.html b/frontend/app/features/rules/pages/rules/rules-page.component.html index d7a77c019..1219480a7 100644 --- a/frontend/app/features/rules/pages/rules/rules-page.component.html +++ b/frontend/app/features/rules/pages/rules/rules-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.rules' | sqxTranslate }} @@ -56,10 +56,10 @@
- @@ -79,7 +79,7 @@ - + diff --git a/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts b/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts index a7b575985..b45d51fa9 100644 --- a/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts +++ b/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts @@ -22,7 +22,7 @@ export class SchemaEditFormComponent implements OnChanges { public fieldForm = new EditSchemaForm(this.formBuilder); - public isEditable = false; + public isEditable ?: boolean | null; constructor( private readonly formBuilder: FormBuilder, diff --git a/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html b/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html index 9836378e9..1e088e603 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html @@ -37,7 +37,7 @@
- +
@@ -64,12 +64,12 @@ + [isLocalizable]="isLocalizable" + [settings]="settings"> diff --git a/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts b/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts index 4fa424248..cd577fd57 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts @@ -7,7 +7,7 @@ import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { FormBuilder } from '@angular/forms'; -import { AddFieldForm, createProperties, EditFieldForm, FieldDto, fieldTypes, LanguagesState, PatternsState, RootFieldDto, SchemaDetailsDto, SchemasState, Types } from '@app/shared'; +import { AddFieldForm, AppSettingsDto, createProperties, EditFieldForm, FieldDto, fieldTypes, LanguagesState, RootFieldDto, SchemaDetailsDto, SchemasState, Types } from '@app/shared'; const DEFAULT_FIELD = { name: '', partitioning: 'invariant', properties: createProperties('String') }; @@ -23,6 +23,9 @@ export class FieldWizardComponent implements OnInit { @Input() public schema: SchemaDetailsDto; + @Input() + public settings: AppSettingsDto; + @Input() public parent: RootFieldDto; @@ -44,9 +47,9 @@ export class FieldWizardComponent implements OnInit { constructor( private readonly formBuilder: FormBuilder, private readonly schemasState: SchemasState, - public readonly languagesState: LanguagesState, - public readonly patternsState: PatternsState - ) {} + public readonly languagesState: LanguagesState + ) { + } public ngOnInit() { if (this.parent) { diff --git a/frontend/app/features/schemas/pages/schema/fields/field.component.html b/frontend/app/features/schemas/pages/schema/fields/field.component.html index 08c52efa5..718a9c0d8 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/field.component.html @@ -94,13 +94,13 @@
- + [isLocalizable]="isLocalizable" + [settings]="settings">
@@ -118,7 +118,7 @@ - + @@ -134,7 +134,10 @@ + [parent]="$any(field)" + [schema]="schema" + [settings]="settings" + (complete)="addFieldDialog.hide()">
diff --git a/frontend/app/features/schemas/pages/schema/fields/field.component.ts b/frontend/app/features/schemas/pages/schema/fields/field.component.ts index 55ca8cf9a..dfb84158e 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/field.component.ts @@ -8,7 +8,7 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { FormBuilder } from '@angular/forms'; -import { createProperties, DialogModel, EditFieldForm, fadeAnimation, LanguageDto, ModalModel, NestedFieldDto, PatternDto, RootFieldDto, SchemaDetailsDto, SchemasState, sorted } from '@app/shared'; +import { AppSettingsDto, createProperties, DialogModel, EditFieldForm, fadeAnimation, LanguageDto, ModalModel, NestedFieldDto, RootFieldDto, SchemaDetailsDto, SchemasState, sorted } from '@app/shared'; @Component({ selector: 'sqx-field', @@ -32,7 +32,7 @@ export class FieldComponent implements OnChanges { public languages: ReadonlyArray; @Input() - public patterns: ReadonlyArray; + public settings: AppSettingsDto; public get isLocalizable() { return (this.parent && this.parent.isLocalizable) || this.field['isLocalizable']; @@ -43,7 +43,7 @@ export class FieldComponent implements OnChanges { public trackByFieldFn: (_index: number, field: NestedFieldDto) => any; public isEditing = false; - public isEditable = false; + public isEditable?: boolean | null; public editForm = new EditFieldForm(this.formBuilder); diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html index 85a94cd33..d020c4a07 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html @@ -96,7 +96,7 @@ [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties" - [patterns]="patterns"> + [settings]="settings">
diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts index 94fe64270..8b2052c0f 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts @@ -7,7 +7,7 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { FieldDto, LanguageDto, PatternDto } from '@app/shared'; +import { AppSettingsDto, FieldDto, LanguageDto } from '@app/shared'; @Component({ selector: 'sqx-field-form-validation', @@ -22,11 +22,11 @@ export class FieldFormValidationComponent { public field: FieldDto; @Input() - public patterns: ReadonlyArray; + public settings: AppSettingsDto; @Input() public languages: ReadonlyArray; @Input() - public isLocalizable: boolean; + public isLocalizable?: boolean | null; } \ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html b/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html index cad4779fd..6ab9a9be7 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html @@ -37,10 +37,11 @@
+ [isLocalizable]="isLocalizable" + [settings]="settings" >
diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts b/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts index 47adfdd74..34105dfec 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts @@ -7,7 +7,7 @@ import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { FieldDto, LanguageDto, PatternDto } from '@app/shared'; +import { AppSettingsDto, FieldDto, LanguageDto } from '@app/shared'; @Component({ selector: 'sqx-field-form', @@ -16,10 +16,10 @@ import { FieldDto, LanguageDto, PatternDto } from '@app/shared'; }) export class FieldFormComponent implements AfterViewInit { @Input() - public showButtons: boolean; + public showButtons?: boolean | null; @Input() - public isEditable: boolean; + public isEditable?: boolean | null; @Input() public fieldForm: FormGroup; @@ -28,13 +28,13 @@ export class FieldFormComponent implements AfterViewInit { public field: FieldDto; @Input() - public patterns: ReadonlyArray; + public settings: AppSettingsDto; @Input() public languages: ReadonlyArray; @Input() - public isLocalizable: boolean; + public isLocalizable?: boolean | null; @Output() public cancel = new EventEmitter(); diff --git a/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.html b/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.html index fed820bab..2d9311304 100644 --- a/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.html @@ -6,15 +6,15 @@ - - + +
- +
@@ -24,10 +24,12 @@
{{ 'schemas.addFieldButton' | sqxTranslate }}
- - - - + + + + \ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts b/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts index 1111411d7..978608b01 100644 --- a/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts @@ -7,7 +7,7 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { Component, Input, OnInit } from '@angular/core'; -import { DialogModel, FieldDto, fieldTypes, LanguagesState, PatternsState, SchemaDetailsDto, SchemasState, sorted } from '@app/shared'; +import { AppsState, DialogModel, FieldDto, fieldTypes, LanguagesState, SchemaDetailsDto, SchemasState, sorted } from '@app/shared'; @Component({ selector: 'sqx-schema-fields', @@ -25,16 +25,14 @@ export class SchemaFieldsComponent implements OnInit { public trackByFieldFn: (_index: number, field: FieldDto) => any; constructor( + public readonly appsState: AppsState, public readonly schemasState: SchemasState, - public readonly languageState: LanguagesState, - public readonly patternsState: PatternsState + public readonly languageState: LanguagesState ) { this.trackByFieldFn = this.trackByField.bind(this); } public ngOnInit() { - this.patternsState.load(); - this.languageState.load(); } diff --git a/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts index 58b3ec8a8..8fb9c17ef 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts @@ -28,7 +28,7 @@ export class AssetsValidationComponent implements OnInit { public languages: ReadonlyArray; @Input() - public isLocalizable: boolean; + public isLocalizable?: boolean | null; public ngOnInit() { this.fieldForm.setControl('minItems', diff --git a/frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.ts index 763d32545..177106f9b 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.ts @@ -29,7 +29,7 @@ export class BooleanValidationComponent implements OnInit { public languages: ReadonlyArray; @Input() - public isLocalizable: boolean; + public isLocalizable?: boolean | null; public showDefaultValue: Observable; diff --git a/frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.html b/frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.html index 6bdc12f1e..e1a23b56e 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.html @@ -3,7 +3,7 @@
- +
@@ -11,7 +11,7 @@
- +
@@ -31,7 +31,7 @@
- +
diff --git a/frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.ts index fa9ea9d8c..957316cbb 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.ts @@ -29,7 +29,7 @@ export class DateTimeValidationComponent implements OnInit { public languages: ReadonlyArray; @Input() - public isLocalizable: boolean; + public isLocalizable?: boolean | null; public showDefaultValues: Observable; public showDefaultValue: Observable; diff --git a/frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.ts index 5887babe7..63336a473 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.ts @@ -28,7 +28,7 @@ export class NumberValidationComponent implements OnInit { public languages: ReadonlyArray; @Input() - public isLocalizable: boolean; + public isLocalizable?: boolean | null; public showUnique: boolean; diff --git a/frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.html b/frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.html index b77afbe3e..3ba079c56 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.html @@ -4,7 +4,7 @@
+ [converter]="(schemasSource.converter | async)!" [suggestions]="(schemasSource.converter | async)?.suggestions">
diff --git a/frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.ts index 03cfa4141..5f1cd8f17 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.ts @@ -28,7 +28,7 @@ export class ReferencesValidationComponent implements OnInit { public languages: ReadonlyArray; @Input() - public isLocalizable: boolean; + public isLocalizable?: boolean | null; constructor( public readonly schemasSource: SchemaTagSource diff --git a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html index f45bae303..429c5080b 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html @@ -34,22 +34,23 @@
- +

{{ 'schemas.fieldTypes.string.suggestions' | sqxTranslate }}

-
+
{{pattern.name}}
-
{{pattern.pattern}}
+
{{pattern.regex}}
+ + + {{patternName}} +
- - {{patternName}} -
diff --git a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.scss b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.scss index 330739795..8c54f65ab 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.scss +++ b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.scss @@ -7,7 +7,7 @@ .control-dropdown { & { max-width: 285px; - min-height: 220px; + min-height: 0; min-width: 200px; } diff --git a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts index 0149db2c9..0d6fb7b8a 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts @@ -7,7 +7,7 @@ import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; -import { fadeAnimation, FieldDto, hasNoValue$, hasValue$, LanguageDto, ModalModel, PatternDto, ResourceOwner, RootFieldDto, StringFieldPropertiesDto, STRING_CONTENT_TYPES, Types, value$ } from '@app/shared'; +import { AppSettingsDto, fadeAnimation, FieldDto, hasNoValue$, hasValue$, LanguageDto, ModalModel, PatternDto, ResourceOwner, RootFieldDto, StringFieldPropertiesDto, STRING_CONTENT_TYPES, Types, value$ } from '@app/shared'; import { Observable } from 'rxjs'; @Component({ @@ -29,13 +29,13 @@ export class StringValidationComponent extends ResourceOwner implements OnChange public properties: StringFieldPropertiesDto; @Input() - public patterns: ReadonlyArray; + public settings: AppSettingsDto; @Input() public languages: ReadonlyArray; @Input() - public isLocalizable: boolean; + public isLocalizable?: boolean | null; public contentTypes = STRING_CONTENT_TYPES; @@ -115,7 +115,7 @@ export class StringValidationComponent extends ResourceOwner implements OnChange } public setPattern(pattern: PatternDto) { - this.fieldForm.controls['pattern'].setValue(pattern.pattern); + this.fieldForm.controls['pattern'].setValue(pattern.regex); this.fieldForm.controls['patternMessage'].setValue(pattern.message); } @@ -125,7 +125,7 @@ export class StringValidationComponent extends ResourceOwner implements OnChange if (!value) { this.patternName = ''; } else { - const matchingPattern = this.patterns.find(x => x.pattern === this.fieldForm.controls['pattern'].value); + const matchingPattern = this.settings.patterns.find(x => x.regex === this.fieldForm.controls['pattern'].value); if (matchingPattern) { this.patternName = matchingPattern.name; diff --git a/frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.ts index e61178725..a21c0ac22 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.ts @@ -28,7 +28,7 @@ export class TagsValidationComponent implements OnInit { public languages: ReadonlyArray; @Input() - public isLocalizable: boolean; + public isLocalizable?: boolean | null; public ngOnInit() { this.fieldForm.setControl('maxItems', diff --git a/frontend/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html b/frontend/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html index 78991ee09..00b7c00cd 100644 --- a/frontend/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html +++ b/frontend/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html @@ -1,4 +1,4 @@ -
+
{{ 'schemas.previewUrls.title' | sqxTranslate }}
@@ -8,11 +8,11 @@
-
+
{{ 'schemas.previewUrls.empty' | sqxTranslate }}
-
+
@@ -54,7 +54,7 @@
diff --git a/frontend/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.html b/frontend/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.html index cfceeb172..0e846fb8a 100644 --- a/frontend/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.html +++ b/frontend/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.html @@ -1,14 +1,14 @@ - +
{{ 'schemas.rules.title' | sqxTranslate }}
-
+
{{ 'schemas.rules.empty' | sqxTranslate }}
-
+
@@ -72,7 +72,7 @@
diff --git a/frontend/app/features/schemas/pages/schema/schema-page.component.html b/frontend/app/features/schemas/pages/schema/schema-page.component.html index 1c2c14679..ed70e9a74 100644 --- a/frontend/app/features/schemas/pages/schema/schema-page.component.html +++ b/frontend/app/features/schemas/pages/schema/schema-page.component.html @@ -1,6 +1,6 @@ - +
- + {{ 'schemas.contextMenuTour' | sqxTranslate }} - + {{ 'schemas.publishedTour' | sqxTranslate }} diff --git a/frontend/app/features/schemas/pages/schemas/schema-form.component.html b/frontend/app/features/schemas/pages/schemas/schema-form.component.html index 643321e68..290fba757 100644 --- a/frontend/app/features/schemas/pages/schemas/schema-form.component.html +++ b/frontend/app/features/schemas/pages/schemas/schema-form.component.html @@ -88,7 +88,7 @@ {{ 'common.hide' | sqxTranslate }} - +
diff --git a/frontend/app/features/schemas/pages/schemas/schemas-page.component.html b/frontend/app/features/schemas/pages/schemas/schemas-page.component.html index 7bc785622..0397486f5 100644 --- a/frontend/app/features/schemas/pages/schemas/schemas-page.component.html +++ b/frontend/app/features/schemas/pages/schemas/schemas-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.schemas' | sqxTranslate }} diff --git a/frontend/app/features/settings/declarations.ts b/frontend/app/features/settings/declarations.ts index fc9ad5074..ea287212c 100644 --- a/frontend/app/features/settings/declarations.ts +++ b/frontend/app/features/settings/declarations.ts @@ -19,8 +19,7 @@ export * from './pages/languages/language-add-form.component'; export * from './pages/languages/language.component'; export * from './pages/languages/languages-page.component'; export * from './pages/more/more-page.component'; -export * from './pages/patterns/pattern.component'; -export * from './pages/patterns/patterns-page.component'; +export * from './pages/settings/settings-page.component'; export * from './pages/plans/plan.component'; export * from './pages/plans/plans-page.component'; export * from './pages/roles/role-add-form.component'; diff --git a/frontend/app/features/settings/module.ts b/frontend/app/features/settings/module.ts index 9e7eed837..324e34e78 100644 --- a/frontend/app/features/settings/module.ts +++ b/frontend/app/features/settings/module.ts @@ -10,7 +10,8 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HelpComponent, HistoryComponent, SqxFrameworkModule, SqxSharedModule } from '@app/shared'; -import { BackupComponent, BackupsPageComponent, ClientAddFormComponent, ClientComponent, ClientConnectFormComponent, ClientsPageComponent, ContributorAddFormComponent, ContributorComponent, ContributorsPageComponent, ImportContributorsDialogComponent, LanguageAddFormComponent, LanguageComponent, LanguagesPageComponent, MorePageComponent, PatternComponent, PatternsPageComponent, PlanComponent, PlansPageComponent, RoleAddFormComponent, RoleComponent, RolesPageComponent, SettingsAreaComponent, WorkflowAddFormComponent, WorkflowComponent, WorkflowsPageComponent, WorkflowStepComponent, WorkflowTransitionComponent } from './declarations'; +import { BackupComponent, BackupsPageComponent, ClientAddFormComponent, ClientComponent, ClientConnectFormComponent, ClientsPageComponent, ContributorAddFormComponent, ContributorComponent, ContributorsPageComponent, ImportContributorsDialogComponent, LanguageAddFormComponent, LanguageComponent, LanguagesPageComponent, MorePageComponent, PlanComponent, PlansPageComponent, RoleAddFormComponent, RoleComponent, RolesPageComponent, SettingsAreaComponent, WorkflowAddFormComponent, WorkflowComponent, WorkflowsPageComponent, WorkflowStepComponent, WorkflowTransitionComponent } from './declarations'; +import { SettingsPageComponent } from './pages/settings/settings-page.component'; import { WorkflowDiagramComponent } from './pages/workflows/workflow-diagram.component'; const routes: Routes = [ @@ -96,24 +97,8 @@ const routes: Routes = [ ] }, { - path: 'patterns', - component: PatternsPageComponent, - children: [ - { - path: 'history', - component: HistoryComponent, - data: { - channel: 'settings.patterns' - } - }, - { - path: 'help', - component: HelpComponent, - data: { - helpPage: '05-integrated/patterns' - } - } - ] + path: 'settings', + component: SettingsPageComponent }, { path: 'plans', @@ -186,13 +171,12 @@ const routes: Routes = [ LanguageComponent, LanguagesPageComponent, MorePageComponent, - PatternComponent, - PatternsPageComponent, PlanComponent, PlansPageComponent, RoleAddFormComponent, RoleComponent, RolesPageComponent, + SettingsPageComponent, SettingsAreaComponent, WorkflowAddFormComponent, WorkflowComponent, diff --git a/frontend/app/features/settings/pages/backups/backups-page.component.html b/frontend/app/features/settings/pages/backups/backups-page.component.html index 4481fda8e..c672de4c8 100644 --- a/frontend/app/features/settings/pages/backups/backups-page.component.html +++ b/frontend/app/features/settings/pages/backups/backups-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.backups' | sqxTranslate }} diff --git a/frontend/app/features/settings/pages/clients/clients-page.component.html b/frontend/app/features/settings/pages/clients/clients-page.component.html index b21a553cb..b16c30c4f 100644 --- a/frontend/app/features/settings/pages/clients/clients-page.component.html +++ b/frontend/app/features/settings/pages/clients/clients-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.clients' | sqxTranslate }} diff --git a/frontend/app/features/settings/pages/contributors/contributor.component.ts b/frontend/app/features/settings/pages/contributors/contributor.component.ts index 0db2ef804..46354c577 100644 --- a/frontend/app/features/settings/pages/contributors/contributor.component.ts +++ b/frontend/app/features/settings/pages/contributors/contributor.component.ts @@ -21,7 +21,7 @@ export class ContributorComponent { public roles: ReadonlyArray; @Input() - public search: string; + public search?: string | RegExp | null; @Input('sqxContributor') public contributor: ContributorDto; diff --git a/frontend/app/features/settings/pages/contributors/contributors-page.component.html b/frontend/app/features/settings/pages/contributors/contributors-page.component.html index 4bbc46b68..0e3da3cff 100644 --- a/frontend/app/features/settings/pages/contributors/contributors-page.component.html +++ b/frontend/app/features/settings/pages/contributors/contributors-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.contributors' | sqxTranslate }} @@ -82,7 +82,7 @@ - diff --git a/frontend/app/features/settings/pages/languages/language.component.html b/frontend/app/features/settings/pages/languages/language.component.html index aa94668d3..41479de3a 100644 --- a/frontend/app/features/settings/pages/languages/language.component.html +++ b/frontend/app/features/settings/pages/languages/language.component.html @@ -44,7 +44,12 @@
-
+
diff --git a/frontend/app/features/settings/pages/languages/language.component.ts b/frontend/app/features/settings/pages/languages/language.component.ts index 735e5bcf7..47c39cdb9 100644 --- a/frontend/app/features/settings/pages/languages/language.component.ts +++ b/frontend/app/features/settings/pages/languages/language.component.ts @@ -27,7 +27,7 @@ export class LanguageComponent implements OnChanges { public otherLanguage: LanguageDto; - public isEditing = false; + public isEditing?: boolean | null; public isEditable = false; public editForm = new EditLanguageForm(this.formBuilder); @@ -78,7 +78,7 @@ export class LanguageComponent implements OnChanges { } } - public removeFallbackLanguage(language: AppLanguageDto) { + public removeFallbackLanguage(language: LanguageDto) { this.fallbackLanguages = this.fallbackLanguages.removed(language); this.fallbackLanguagesNew = [...this.fallbackLanguagesNew, language].sortedByString(x => x.iso2Code); diff --git a/frontend/app/features/settings/pages/languages/languages-page.component.html b/frontend/app/features/settings/pages/languages/languages-page.component.html index 761230ad7..7ebc4b85f 100644 --- a/frontend/app/features/settings/pages/languages/languages-page.component.html +++ b/frontend/app/features/settings/pages/languages/languages-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.languages' | sqxTranslate }} @@ -23,7 +23,8 @@ [fallbackLanguagesNew]="languageInfo.fallbackLanguagesNew"> - +
diff --git a/frontend/app/features/settings/pages/languages/languages-page.component.ts b/frontend/app/features/settings/pages/languages/languages-page.component.ts index 8f3bf052e..421e6cd2e 100644 --- a/frontend/app/features/settings/pages/languages/languages-page.component.ts +++ b/frontend/app/features/settings/pages/languages/languages-page.component.ts @@ -6,7 +6,7 @@ */ import { Component, OnInit } from '@angular/core'; -import { AppLanguageDto, LanguagesState } from '@app/shared'; +import { LanguagesState, SnapshotLanguage } from '@app/shared'; @Component({ selector: 'sqx-languages-page', @@ -27,7 +27,7 @@ export class LanguagesPageComponent implements OnInit { this.languagesState.load(true); } - public trackByLanguage(_index: number, language: { language: AppLanguageDto }) { + public trackByLanguage(_index: number, language: SnapshotLanguage) { return language.language.iso2Code; } } \ No newline at end of file diff --git a/frontend/app/features/settings/pages/more/more-page.component.ts b/frontend/app/features/settings/pages/more/more-page.component.ts index a4ba8ca93..bc9488a83 100644 --- a/frontend/app/features/settings/pages/more/more-page.component.ts +++ b/frontend/app/features/settings/pages/more/more-page.component.ts @@ -18,7 +18,7 @@ import { AppDto, AppsState, defined, ResourceOwner, Types, UpdateAppForm } from export class MorePageComponent extends ResourceOwner implements OnInit { public app: AppDto; - public isEditable: boolean; + public isEditable = false; public isImageEditable: boolean; public isDeletable: boolean; @@ -49,7 +49,7 @@ export class MorePageComponent extends ResourceOwner implements OnInit { this.updateForm.setEnabled(this.isEditable); })); - this.appsState.reloadSelected(); + this.appsState.reloadApps(); } public save() { diff --git a/frontend/app/features/settings/pages/patterns/pattern.component.html b/frontend/app/features/settings/pages/patterns/pattern.component.html deleted file mode 100644 index 85186665d..000000000 --- a/frontend/app/features/settings/pages/patterns/pattern.component.html +++ /dev/null @@ -1,46 +0,0 @@ -
- -
- - - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
\ No newline at end of file diff --git a/frontend/app/features/settings/pages/patterns/pattern.component.scss b/frontend/app/features/settings/pages/patterns/pattern.component.scss deleted file mode 100644 index b281dc67e..000000000 --- a/frontend/app/features/settings/pages/patterns/pattern.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.col-options { - @include force-width(6rem); -} - -.col-name, -.col-message { - @include force-width(10rem); -} \ No newline at end of file diff --git a/frontend/app/features/settings/pages/patterns/pattern.component.ts b/frontend/app/features/settings/pages/patterns/pattern.component.ts deleted file mode 100644 index 082bd698a..000000000 --- a/frontend/app/features/settings/pages/patterns/pattern.component.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; -import { EditPatternForm, PatternDto, PatternsState } from '@app/shared'; - -@Component({ - selector: 'sqx-pattern', - styleUrls: ['./pattern.component.scss'], - templateUrl: './pattern.component.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PatternComponent implements OnChanges { - @Input() - public pattern: PatternDto; - - public editForm = new EditPatternForm(this.formBuilder); - - public isEditable = true; - public isDeletable = false; - - constructor( - private readonly patternsState: PatternsState, - private readonly formBuilder: FormBuilder - ) { - } - - public ngOnChanges(changes: SimpleChanges) { - if (changes['pattern']) { - this.isEditable = !this.pattern || this.pattern.canUpdate; - this.isDeletable = this.pattern && this.pattern.canDelete; - - this.editForm.load(this.pattern); - this.editForm.setEnabled(this.isEditable); - } - } - - public cancel() { - this.editForm.submitCompleted({ newValue: this.pattern }); - } - - public delete() { - this.patternsState.delete(this.pattern); - } - - public save() { - if (!this.isEditable) { - return; - } - - const value = this.editForm.submit(); - - if (value) { - if (this.pattern) { - this.patternsState.update(this.pattern, value) - .subscribe(() => { - this.editForm.submitCompleted({ noReset: true }); - }, error => { - this.editForm.submitFailed(error); - }); - } else { - this.patternsState.create(value) - .subscribe(() => { - this.editForm.submitCompleted(); - }, error => { - this.editForm.submitFailed(error); - }); - } - } - } -} \ No newline at end of file diff --git a/frontend/app/features/settings/pages/patterns/patterns-page.component.html b/frontend/app/features/settings/pages/patterns/patterns-page.component.html deleted file mode 100644 index ff5e0978e..000000000 --- a/frontend/app/features/settings/pages/patterns/patterns-page.component.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - {{ 'common.patterns' | sqxTranslate }} - - - - - - - - - - -
- -
- {{ 'patterns.empty' | sqxTranslate }} -
- - - - - -
-
-
-
- - -
- - - - - - - -
-
-
- - \ No newline at end of file diff --git a/frontend/app/features/settings/pages/patterns/patterns-page.component.scss b/frontend/app/features/settings/pages/patterns/patterns-page.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/app/features/settings/pages/patterns/patterns-page.component.ts b/frontend/app/features/settings/pages/patterns/patterns-page.component.ts deleted file mode 100644 index 8388923ea..000000000 --- a/frontend/app/features/settings/pages/patterns/patterns-page.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, OnInit } from '@angular/core'; -import { PatternDto, PatternsState } from '@app/shared'; - -@Component({ - selector: 'sqx-patterns-page', - styleUrls: ['./patterns-page.component.scss'], - templateUrl: './patterns-page.component.html' -}) -export class PatternsPageComponent implements OnInit { - constructor( - public readonly patternsState: PatternsState - ) { - } - - public ngOnInit() { - this.patternsState.load(); - } - - public reload() { - this.patternsState.load(true); - } - - public trackByPattern(_index: number, pattern: PatternDto) { - return pattern.id; - } -} \ No newline at end of file diff --git a/frontend/app/features/settings/pages/plans/plan.component.html b/frontend/app/features/settings/pages/plans/plan.component.html index 44aa3c0dc..ccf78ad74 100644 --- a/frontend/app/features/settings/pages/plans/plan.component.html +++ b/frontend/app/features/settings/pages/plans/plan.component.html @@ -30,7 +30,7 @@ confirmRememberKey="changePlan" confirmTitle="i18n:plans.changeConfirmTitle" [confirmText]="planInfo.plan.confirmText" - [confirmRequired]="planInfo.plan.confirmText"> + [confirmRequired]="!!planInfo.plan.confirmText"> {{ 'plans.change' | sqxTranslate }}
@@ -48,8 +48,8 @@
diff --git a/frontend/app/features/settings/pages/plans/plans-page.component.html b/frontend/app/features/settings/pages/plans/plans-page.component.html index ece9087e4..f5936f6f1 100644 --- a/frontend/app/features/settings/pages/plans/plans-page.component.html +++ b/frontend/app/features/settings/pages/plans/plans-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.subscription' | sqxTranslate }} diff --git a/frontend/app/features/settings/pages/roles/roles-page.component.html b/frontend/app/features/settings/pages/roles/roles-page.component.html index 5a3a8b1b9..ae2a4c16d 100644 --- a/frontend/app/features/settings/pages/roles/roles-page.component.html +++ b/frontend/app/features/settings/pages/roles/roles-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.roles' | sqxTranslate }} @@ -18,7 +18,7 @@
+ [schemas]="(schemasState.schemas | async)!" [allPermissions]="allPermissions"> diff --git a/frontend/app/features/settings/pages/settings/settings-page.component.html b/frontend/app/features/settings/pages/settings/settings-page.component.html new file mode 100644 index 000000000..52651e0f7 --- /dev/null +++ b/frontend/app/features/settings/pages/settings/settings-page.component.html @@ -0,0 +1,149 @@ + + + + + {{ 'appSettings.title' | sqxTranslate }} + + + + + + + + + + + +
+
{{ 'patterns.title' | sqxTranslate }}
+ +
+
+
+ {{ 'patterns.empty' | sqxTranslate }} +
+ +
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ +
+
+ +
+
+
{{ 'common.name' | sqxTranslate }}
+
+ +
+
{{ 'common.pattern' | sqxTranslate }}
+
+ +
+
{{ 'common.message' | sqxTranslate }}
+
+ +
+ +
+
+
+
+
+ +
+
{{ 'editors.title' | sqxTranslate }}
+ +
+
+
+ {{ 'editors.empty' | sqxTranslate }} +
+ +
+
+ + + +
+ +
+ + + +
+ +
+ +
+
+ +
+
+
{{ 'common.name' | sqxTranslate }}
+
+ +
+
{{ 'common.url' | sqxTranslate }}
+
+ +
+ +
+
+
+
+
+ +
+
{{ 'common.contents' | sqxTranslate }}
+ +
+
+ + + +
+
+
+
+
\ No newline at end of file diff --git a/frontend/app/features/settings/pages/settings/settings-page.component.scss b/frontend/app/features/settings/pages/settings/settings-page.component.scss new file mode 100644 index 000000000..42fc70fdb --- /dev/null +++ b/frontend/app/features/settings/pages/settings/settings-page.component.scss @@ -0,0 +1,6 @@ +.preview { + background-color: $color-input; + border: 0; + border-radius: 4px; + opacity: .4; +} \ No newline at end of file diff --git a/frontend/app/features/settings/pages/settings/settings-page.component.ts b/frontend/app/features/settings/pages/settings/settings-page.component.ts new file mode 100644 index 000000000..554e4a0ca --- /dev/null +++ b/frontend/app/features/settings/pages/settings/settings-page.component.ts @@ -0,0 +1,67 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { AppSettingsDto, AppsState, EditAppSettingsForm, ResourceOwner } from '@app/shared'; + +@Component({ + selector: 'sqx-settings-page', + styleUrls: ['./settings-page.component.scss'], + templateUrl: './settings-page.component.html' +}) +export class SettingsPageComponent extends ResourceOwner implements OnInit { + public isEditable = false; + + public editForm = new EditAppSettingsForm(this.formBuilder); + public editingSettings: AppSettingsDto; + + constructor( + private readonly appsState: AppsState, + private readonly formBuilder: FormBuilder + ) { + super(); + } + + public ngOnInit() { + this.appsState.loadSettings(); + + this.own( + this.appsState.selectedSettings + .subscribe(settings => { + if (settings) { + this.isEditable = settings.canUpdate; + + this.editForm.load(settings); + this.editForm.setEnabled(settings.canUpdate); + + this.editingSettings = settings; + } + })); + } + + public reload() { + this.appsState.loadSettings(true); + } + + public save() { + if (!this.isEditable) { + return; + } + + const value = this.editForm.submit(); + + if (value) { + this.appsState.updateSettings(this.editingSettings, value) + .subscribe(() => { + this.editForm.submitCompleted({ noReset: true }); + }, error => { + this.editForm.submitFailed(error); + }); + } + } +} \ No newline at end of file diff --git a/frontend/app/features/settings/pages/workflows/workflow-step.component.html b/frontend/app/features/settings/pages/workflows/workflow-step.component.html index 294a146f4..496445e42 100644 --- a/frontend/app/features/settings/pages/workflows/workflow-step.component.html +++ b/frontend/app/features/settings/pages/workflows/workflow-step.component.html @@ -10,7 +10,7 @@
@@ -63,7 +63,7 @@
@@ -78,7 +78,7 @@
; @Input() - public disabled: boolean; + public disabled?: boolean | null; public openSteps: ReadonlyArray; public openStep: WorkflowStep; @@ -55,7 +55,7 @@ export class WorkflowStepComponent implements OnChanges { public transitions: ReadonlyArray; public ngOnChanges(changes: SimpleChanges) { - if (changes['workflow'] || changes['step'] || false) { + if (changes['workflow'] || changes['step']) { this.openSteps = this.workflow.getOpenSteps(this.step); this.openStep = this.openSteps[0]; @@ -87,7 +87,7 @@ export class WorkflowStepComponent implements OnChanges { this.update.emit({ noUpdateRoles }); } - public trackByTransition(_index: number, transition: WorkflowTransition) { + public trackByTransition(_index: number, transition: WorkflowTransitionView) { return transition.to; } } \ No newline at end of file diff --git a/frontend/app/features/settings/pages/workflows/workflow-transition.component.html b/frontend/app/features/settings/pages/workflows/workflow-transition.component.html index 23040dd60..f5ffe2a4e 100644 --- a/frontend/app/features/settings/pages/workflows/workflow-transition.component.html +++ b/frontend/app/features/settings/pages/workflows/workflow-transition.component.html @@ -12,7 +12,7 @@
; @Input() - public disabled: boolean; + public disabled?: boolean | null; public changeExpression(expression: string) { this.update.emit({ expression }); diff --git a/frontend/app/features/settings/pages/workflows/workflow.component.html b/frontend/app/features/settings/pages/workflows/workflow.component.html index 1b03cee52..7f4d7a089 100644 --- a/frontend/app/features/settings/pages/workflows/workflow.component.html +++ b/frontend/app/features/settings/pages/workflows/workflow.component.html @@ -5,10 +5,10 @@ {{workflow.displayName}}
- +
@@ -78,7 +78,7 @@
- diff --git a/frontend/app/features/settings/pages/workflows/workflows-page.component.html b/frontend/app/features/settings/pages/workflows/workflows-page.component.html index 70fc6d01d..cc0cd2aa8 100644 --- a/frontend/app/features/settings/pages/workflows/workflows-page.component.html +++ b/frontend/app/features/settings/pages/workflows/workflows-page.component.html @@ -1,6 +1,6 @@ - + {{ 'common.workflows' | sqxTranslate }} diff --git a/frontend/app/features/settings/settings-area.component.html b/frontend/app/features/settings/settings-area.component.html index 16309b465..d3c1dde8e 100644 --- a/frontend/app/features/settings/settings-area.component.html +++ b/frontend/app/features/settings/settings-area.component.html @@ -39,9 +39,9 @@ - diff --git a/frontend/app/framework/angular/avatar.component.ts b/frontend/app/framework/angular/avatar.component.ts index c3ac0b4b7..cc6d5120c 100644 --- a/frontend/app/framework/angular/avatar.component.ts +++ b/frontend/app/framework/angular/avatar.component.ts @@ -16,10 +16,10 @@ import { picasso } from '@app/framework/internal'; }) export class AvatarComponent implements OnChanges { @Input() - public identifier: string; + public identifier: string | undefined | null; @Input() - public image: string; + public image: string | undefined | null; @Input() public size = 50; diff --git a/frontend/app/framework/angular/forms/confirm-click.directive.ts b/frontend/app/framework/angular/forms/confirm-click.directive.ts index f32cb631f..9770207c9 100644 --- a/frontend/app/framework/angular/forms/confirm-click.directive.ts +++ b/frontend/app/framework/angular/forms/confirm-click.directive.ts @@ -17,16 +17,16 @@ import { take } from 'rxjs/operators'; }) export class ConfirmClickDirective { @Input() - public confirmTitle: string; + public confirmTitle: string | undefined | null; @Input() - public confirmText: string; + public confirmText: string | undefined | null; @Input() public confirmRememberKey: string; @Input() - public confirmRequired = true; + public confirmRequired?: boolean | null = true; @Output() public beforeClick = new EventEmitter(); diff --git a/frontend/app/framework/angular/forms/editable-title.component.ts b/frontend/app/framework/angular/forms/editable-title.component.ts index 3de5d6768..8499a31b3 100644 --- a/frontend/app/framework/angular/forms/editable-title.component.ts +++ b/frontend/app/framework/angular/forms/editable-title.component.ts @@ -19,7 +19,7 @@ export class EditableTitleComponent { public nameChange = new EventEmitter(); @Input() - public disabled = false; + public disabled?: boolean | null; @Input() public fallback: string; diff --git a/frontend/app/framework/angular/forms/editors/autocomplete.component.ts b/frontend/app/framework/angular/forms/editors/autocomplete.component.ts index 0466cecac..bfa708329 100644 --- a/frontend/app/framework/angular/forms/editors/autocomplete.component.ts +++ b/frontend/app/framework/angular/forms/editors/autocomplete.component.ts @@ -69,7 +69,7 @@ export class AutocompleteComponent extends StatefulControlComponent diff --git a/frontend/app/framework/angular/forms/editors/code-editor.component.ts b/frontend/app/framework/angular/forms/editors/code-editor.component.ts index 9ce641310..ee7ec0a69 100644 --- a/frontend/app/framework/angular/forms/editors/code-editor.component.ts +++ b/frontend/app/framework/angular/forms/editors/code-editor.component.ts @@ -38,7 +38,7 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im public editor: ElementRef; @Input() - public noBorder = false; + public noBorder?: boolean | null; @Input() public mode = 'ace/mode/javascript'; @@ -53,7 +53,7 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im public height = 0; @Input() - public set completion(value: ReadonlyArray<{ name: string, description: string }> | undefined) { + public set completion(value: ReadonlyArray<{ name: string, description: string }> | undefined | null) { if (value) { this.completions = value.map(({ name, description }) => ({ value: name, name, meta: 'context', description })); } else { diff --git a/frontend/app/framework/angular/forms/editors/color-picker.component.ts b/frontend/app/framework/angular/forms/editors/color-picker.component.ts index 77039eade..59a49e443 100644 --- a/frontend/app/framework/angular/forms/editors/color-picker.component.ts +++ b/frontend/app/framework/angular/forms/editors/color-picker.component.ts @@ -16,7 +16,7 @@ export const SQX_COLOR_PICKER_CONTROL_VALUE_ACCESSOR: any = { interface State { // The current value. - value?: string; + value: string; // The foreground color. foreground: string; @@ -48,7 +48,7 @@ export class ColorPickerComponent extends StatefulControlComponent { if (open) { diff --git a/frontend/app/framework/angular/forms/editors/date-time-editor.component.ts b/frontend/app/framework/angular/forms/editors/date-time-editor.component.ts index e1871775c..0f484d04d 100644 --- a/frontend/app/framework/angular/forms/editors/date-time-editor.component.ts +++ b/frontend/app/framework/angular/forms/editors/date-time-editor.component.ts @@ -48,19 +48,19 @@ export class DateTimeEditorComponent extends StatefulControlComponent; diff --git a/frontend/app/framework/angular/forms/editors/dropdown.component.ts b/frontend/app/framework/angular/forms/editors/dropdown.component.ts index 7169e5eaf..73372e59a 100644 --- a/frontend/app/framework/angular/forms/editors/dropdown.component.ts +++ b/frontend/app/framework/angular/forms/editors/dropdown.component.ts @@ -43,7 +43,7 @@ export class DropdownComponent extends StatefulControlComponent = []; + public items: ReadonlyArray | undefined | null = []; @Input() public searchProperty = 'name'; @@ -52,10 +52,10 @@ export class DropdownComponent extends StatefulControlComponent; @@ -226,7 +226,7 @@ export class DropdownComponent extends StatefulControlComponent; @Input() - public type: 'text' | 'boolean' | 'datetime' | 'date' | 'tags' = 'text'; + public type: 'text' | 'boolean' | 'datetime' | 'date' | 'tags' | 'number' = 'text'; @Input() public name: string; diff --git a/frontend/app/framework/angular/forms/editors/tag-editor.component.ts b/frontend/app/framework/angular/forms/editors/tag-editor.component.ts index 0434b8507..007b2b660 100644 --- a/frontend/app/framework/angular/forms/editors/tag-editor.component.ts +++ b/frontend/app/framework/angular/forms/editors/tag-editor.component.ts @@ -12,8 +12,6 @@ import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { fadeAnimation, getTagValues, Keys, ModalModel, StatefulControlComponent, StringConverter, TagValue, Types } from '@app/framework/internal'; import { distinctUntilChanged, map, tap } from 'rxjs/operators'; -export const CONVERSION_FAILED = {}; - export const SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TagEditorComponent), multi: true }; @@ -62,28 +60,28 @@ export class TagEditorComponent extends StatefulControlComponent) { + public set suggestions(value: ReadonlyArray | undefined | null) { this.suggestionsSorted = getTagValues(value); } @Input() - public set disabled(value: boolean) { - this.setDisabledState(value); + public set disabled(value: boolean | undefined | null) { + this.setDisabledState(value === true); } public suggestionsSorted: ReadonlyArray = []; diff --git a/frontend/app/framework/angular/forms/editors/toggle.component.ts b/frontend/app/framework/angular/forms/editors/toggle.component.ts index 6816dbd16..24f4ad3a7 100644 --- a/frontend/app/framework/angular/forms/editors/toggle.component.ts +++ b/frontend/app/framework/angular/forms/editors/toggle.component.ts @@ -28,7 +28,7 @@ interface State { }) export class ToggleComponent extends StatefulControlComponent { @Input() - public threeStates = false; + public threeStates?: boolean | null; public set disabled(value: boolean) { this.setDisabledState(value); @@ -58,10 +58,8 @@ export class ToggleComponent extends StatefulControlComponent @@ -24,7 +24,7 @@ export class FocusOnInitDirective implements AfterViewInit { } public ngAfterViewInit() { - if (this.enabled === false) { + if (!this.enabled) { return; } diff --git a/frontend/app/framework/angular/forms/form-alert.component.ts b/frontend/app/framework/angular/forms/form-alert.component.ts index e9dc53386..8c1bc604d 100644 --- a/frontend/app/framework/angular/forms/form-alert.component.ts +++ b/frontend/app/framework/angular/forms/form-alert.component.ts @@ -18,11 +18,11 @@ export class FormAlertComponent { public class: string; @Input() - public marginTop = 2; + public marginTop: number | string | undefined | null = 2; @Input() - public marginBottom = 4; + public marginBottom: number | string | undefined | null = 4; @Input() - public light = false; + public light?: boolean | null; } \ No newline at end of file diff --git a/frontend/app/framework/angular/forms/form-error.component.ts b/frontend/app/framework/angular/forms/form-error.component.ts index ce5f31531..065ddd509 100644 --- a/frontend/app/framework/angular/forms/form-error.component.ts +++ b/frontend/app/framework/angular/forms/form-error.component.ts @@ -19,10 +19,10 @@ export class FormErrorComponent implements OnChanges { public error?: ErrorDto | null; @Input() - public bubble = false; + public bubble?: boolean | null; @Input() - public closeable = false; + public closeable?: boolean | null; public show: boolean; diff --git a/frontend/app/framework/angular/forms/form-hint.component.ts b/frontend/app/framework/angular/forms/form-hint.component.ts index c82d2ef2e..e2b5b9d45 100644 --- a/frontend/app/framework/angular/forms/form-hint.component.ts +++ b/frontend/app/framework/angular/forms/form-hint.component.ts @@ -18,8 +18,8 @@ export class FormHintComponent { public class: string; @Input() - public marginTop = 0; + public marginTop: number | string = 0; @Input() - public marginBottom = 0; + public marginBottom: number | string = 0; } \ No newline at end of file diff --git a/frontend/app/framework/angular/forms/forms-helper.ts b/frontend/app/framework/angular/forms/forms-helper.ts index a376acbe2..aeae1eb59 100644 --- a/frontend/app/framework/angular/forms/forms-helper.ts +++ b/frontend/app/framework/angular/forms/forms-helper.ts @@ -57,7 +57,7 @@ export function getControlPath(control: AbstractControl | undefined | null, apiC name = key; } } - } else if (control.parent instanceof FormArray) { + } else if (control.parent) { for (let i = 0; i < control.parent.controls.length; i++) { if (control.parent.controls[i] === control) { if (apiCompatible) { diff --git a/frontend/app/framework/angular/forms/indeterminate-value.directive.ts b/frontend/app/framework/angular/forms/indeterminate-value.directive.ts index 0dccd8e8a..3c0dc1ac6 100644 --- a/frontend/app/framework/angular/forms/indeterminate-value.directive.ts +++ b/frontend/app/framework/angular/forms/indeterminate-value.directive.ts @@ -40,10 +40,8 @@ export class IndeterminateValueDirective implements ControlValueAccessor { if (this.threeStates) { if (isChecked) { isChecked = null; - } else if (isChecked === null) { - isChecked = false; } else { - isChecked = true; + isChecked = isChecked !== null; } } else { isChecked = !(isChecked === true); diff --git a/frontend/app/framework/angular/forms/model.ts b/frontend/app/framework/angular/forms/model.ts index 7e758bed4..f3f5e94b0 100644 --- a/frontend/app/framework/angular/forms/model.ts +++ b/frontend/app/framework/angular/forms/model.ts @@ -122,7 +122,7 @@ export class Form { this.enable(); } - private updateSubmitState(errorOrMessage: string | ErrorDto | null | undefined, submitting: boolean, replaceDetails = true) { + private updateSubmitState(errorOrMessage: string | ErrorDto | undefined | null, submitting: boolean, replaceDetails = true) { const error = getError(errorOrMessage); this.state.next(s => ({ diff --git a/frontend/app/framework/angular/forms/progress-bar.component.ts b/frontend/app/framework/angular/forms/progress-bar.component.ts index 4f8d3d0a2..4b009c99e 100644 --- a/frontend/app/framework/angular/forms/progress-bar.component.ts +++ b/frontend/app/framework/angular/forms/progress-bar.component.ts @@ -37,10 +37,10 @@ export class ProgressBarComponent implements OnChanges, OnInit { public strokeWidth = 4; @Input() - public showText = true; + public showText?: boolean | null = true; @Input() - public animated = true; + public animated?: boolean | null = true; @Input() public value = 0; diff --git a/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts b/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts index 99007962f..43309e294 100644 --- a/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts +++ b/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts @@ -17,7 +17,7 @@ describe('UndefinableFormArray', () => { value: ['1'] }]; - tests.forEach(x => { + tests.map(x => { it(`should set value as ${x.name}`, () => { const control = new UndefinableFormArray([ @@ -30,7 +30,7 @@ describe('UndefinableFormArray', () => { }); }); - tests.forEach(x => { + tests.map(x => { it(`should patch value as ${x.name}`, () => { const control = new UndefinableFormArray([ @@ -43,7 +43,7 @@ describe('UndefinableFormArray', () => { }); }); - tests.forEach(x => { + tests.map(x => { it(`should reset value as ${x.name}`, () => { const control = new UndefinableFormArray([ diff --git a/frontend/app/framework/angular/http/caching.interceptor.ts b/frontend/app/framework/angular/http/caching.interceptor.ts index 1d82083c8..a8251af17 100644 --- a/frontend/app/framework/angular/http/caching.interceptor.ts +++ b/frontend/app/framework/angular/http/caching.interceptor.ts @@ -16,7 +16,7 @@ export class CachingInterceptor implements HttpInterceptor { private readonly cache: { [url: string]: HttpResponse } = {}; public intercept(req: HttpRequest, next: HttpHandler): Observable> { - if (req.method === 'GET' && req.reportProgress === false) { + if (req.method === 'GET' && !req.reportProgress) { const cacheEntry = this.cache[req.url]; if (cacheEntry) { diff --git a/frontend/app/framework/angular/language-selector.component.html b/frontend/app/framework/angular/language-selector.component.html index 21e7221d2..bc5dac45e 100644 --- a/frontend/app/framework/angular/language-selector.component.html +++ b/frontend/app/framework/angular/language-selector.component.html @@ -1,5 +1,5 @@
-
@@ -11,7 +11,7 @@