Browse Source

Context changed.

pull/1116/head
Sebastian Stehle 2 years ago
parent
commit
5ab97b354d
  1. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldEditor.cs
  2. 7
      backend/src/Squidex/wwwroot/scripts/editor-context.html
  3. 6
      backend/src/Squidex/wwwroot/scripts/editor-log.html
  4. 32
      backend/src/Squidex/wwwroot/scripts/editor-sdk.js
  5. 27
      frontend/src/app/features/content/pages/content/content-page.component.ts
  6. 2
      frontend/src/app/features/content/pages/schemas/schemas-page.component.html
  7. 6
      frontend/src/app/features/content/shared/forms/field-editor.component.html
  8. 2
      frontend/src/app/features/content/shared/forms/field-editor.component.ts
  9. 8
      frontend/src/app/features/content/shared/forms/iframe-editor.component.ts
  10. 12
      frontend/src/app/features/content/shared/references/references-checkboxes.component.ts
  11. 1
      frontend/src/app/features/content/shared/references/references-radio-buttons.component.html
  12. 2
      frontend/src/app/features/content/shared/references/references-radio-buttons.component.scss
  13. 135
      frontend/src/app/features/content/shared/references/references-radio-buttons.component.ts
  14. 2
      frontend/src/app/features/schemas/pages/schemas/schemas-page.component.html
  15. 2
      frontend/src/app/shared/components/schema-category.component.html
  16. 3
      frontend/src/app/shared/services/schemas.types.ts
  17. 4
      frontend/src/app/shared/state/schemas.state.ts

3
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldEditor.cs

@ -13,5 +13,6 @@ public enum ReferencesFieldEditor
Dropdown,
Tags,
Checkboxes,
Input
Input,
Radio
}

7
backend/src/Squidex/wwwroot/scripts/editor-context.html

@ -43,6 +43,13 @@
grow(element);
});
// Init is called once with a context that contains the app name, schema name and authentication information.
field.onContextChanged(function (context) {
element.innerHTML = JSON.stringify(context, null, 2);
grow(element);
});
</script>
</body>

6
backend/src/Squidex/wwwroot/scripts/editor-log.html

@ -78,7 +78,7 @@
appendLabel('Value of Form');
appendLine(`<${JSON.stringify(field.getFormValue(), 2)}>`);
appendLabel('Disabled: ');
appendLabel('Disabled');
appendLine(field.isDisabled());
console.log(text);
@ -124,6 +124,10 @@
logState('Field language changed');
});
field.onContextChanged(function () {
logState('Context changed');
});
field.onExpanded(function () {
logState('Expanded changed');
});

32
backend/src/Squidex/wwwroot/scripts/editor-sdk.js

@ -259,6 +259,7 @@ function SquidexWidget(options) {
*/
function SquidexFormField(options) {
var context;
var contextHandler;
var currentConfirm;
var currentPickAssets;
var currentPickContents;
@ -324,6 +325,12 @@ function SquidexFormField(options) {
}
}
function raiseContextChanged() {
if (contextHandler && context) {
contextHandler(context);
}
}
function raisedMoved() {
if (movedHandler && isNumber(index)) {
movedHandler(index);
@ -386,6 +393,10 @@ function SquidexFormField(options) {
context = event.data.context;
raiseInit();
} else if (type === 'contextChanged') {
context = event.data.context;
raiseContextChanged();
} else if (type === 'confirmResult') {
var correlationId = event.data.correlationId;
@ -631,7 +642,6 @@ function SquidexFormField(options) {
}
initHandler = callback;
raiseInit();
},
@ -661,10 +671,23 @@ function SquidexFormField(options) {
}
disabledHandler = callback;
raiseDisabled();
},
/**
* Register an function that is called whenever the context has been changed.
*
* @param {function} callback: The callback to invoke. Argument 1: New context.
*/
onContextChanged: function (callback) {
if (!isFunction(callback)) {
return;
}
contextHandler = callback;
raiseContextChanged();
},
/**
* Register an function that is called whenever the field language is changed.
*
@ -676,7 +699,6 @@ function SquidexFormField(options) {
}
languageHandler = callback;
raiseLanguageChanged();
},
@ -691,7 +713,6 @@ function SquidexFormField(options) {
}
valueHandler = callback;
raiseValueChanged();
},
@ -706,7 +727,6 @@ function SquidexFormField(options) {
}
formValueHandler = callback;
raiseFormValueChanged();
},
@ -721,7 +741,6 @@ function SquidexFormField(options) {
}
fullscreenHandler = callback;
raiseFullscreen();
},
@ -736,7 +755,6 @@ function SquidexFormField(options) {
}
expandedHandler = callback;
raiseExpanded();
},

27
frontend/src/app/features/content/pages/content/content-page.component.ts

@ -62,6 +62,7 @@ import { ContentReferencesComponent } from './references/content-references.comp
})
export class ContentPageComponent implements CanComponentDeactivate, OnInit {
private readonly subscriptions = new Subscriptions();
private readonly mutableContext: Record<string, any>;
private autoSaveKey!: AutoSaveKey;
public schema!: SchemaDto;
@ -69,8 +70,8 @@ export class ContentPageComponent implements CanComponentDeactivate, OnInit {
public formContext: any;
public contentTab = this.route.queryParams.pipe(map(x => x['tab'] || 'editor'));
public content?: ContentDto | null;
public contentId = '';
public content?: ContentDto | null;
public contentVersion: Version | null = null;
public contentForm!: EditContentForm;
public contentFormCompare: EditContentForm | null = null;
@ -103,7 +104,7 @@ export class ContentPageComponent implements CanComponentDeactivate, OnInit {
) {
const role = appsState.snapshot.selectedApp?.roleName;
this.formContext = {
this.mutableContext = {
apiUrl: apiUrl.buildUrl('api'),
appId: contentsState.appId,
appName: contentsState.appName,
@ -158,10 +159,10 @@ export class ContentPageComponent implements CanComponentDeactivate, OnInit {
this.schema = schema;
const languageKey = this.localStore.get(this.languageKey());
const language = this.languages.find(x => x.iso2Code === languageKey);
const languageItem = this.languages.find(x => x.iso2Code === languageKey);
if (language) {
this.language = language;
if (languageItem) {
this.language = languageItem;
}
this.contentForm = new EditContentForm(this.languages, this.schema, this.schemasState.schemaMap, this.formContext);
@ -172,12 +173,9 @@ export class ContentPageComponent implements CanComponentDeactivate, OnInit {
.subscribe(content => {
const isNewContent = isOtherContent(content, this.content);
this.formContext['languages'] = this.languages;
this.formContext['schema'] = this.schema;
this.formContext['initialContent'] = content;
this.contentForm.setContext(this.formContext);
this.content = content;
this.updateContext();
this.contentForm.setContext(this.formContext);
this.autoSaveKey = {
schemaId: this.schema.id,
@ -221,6 +219,14 @@ export class ContentPageComponent implements CanComponentDeactivate, OnInit {
}));
}
private updateContext() {
this.mutableContext['initialContent'] = this.content;
this.mutableContext['language'] = this.language;
this.mutableContext['languages'] = this.languages;
this.mutableContext['schema'] = this.schema;
this.formContext = { ...this.mutableContext };
}
public canDeactivate(): Observable<boolean> {
return this.checkPendingChangesBeforeClose().pipe(
tap(confirmed => {
@ -308,6 +314,7 @@ export class ContentPageComponent implements CanComponentDeactivate, OnInit {
public changeLanguage(language: AppLanguageDto) {
this.language = language;
this.updateContext();
this.localStore.set(this.languageKey(), language.iso2Code);
}

2
frontend/src/app/features/content/pages/schemas/schemas-page.component.html

@ -20,7 +20,7 @@
</li>
</ul>
<ng-container>
@for (category of categories | async; track category.name) {
@for (category of categories | async; track category.displayName) {
<sqx-schema-category [schemaCategory]="category" [schemaTarget]="'Contents'"></sqx-schema-category>
}
</ng-container>

6
frontend/src/app/features/content/shared/forms/field-editor.component.html

@ -203,6 +203,12 @@
[language]="language"
[schemaId]="field.rawProperties.singleId"></sqx-references-checkboxes>
}
@case ("Radio") {
<sqx-references-radio-buttons
[formControl]="$any(fieldForm)"
[language]="language"
[schemaId]="field.rawProperties.singleId"></sqx-references-radio-buttons>
}
}
}
@case ("String") {

2
frontend/src/app/features/content/shared/forms/field-editor.component.ts

@ -13,6 +13,7 @@ import { AbstractContentForm, AnnotationCreate, AnnotationsSelect, AppLanguageDt
import { ReferenceDropdownComponent } from '../references/reference-dropdown.component';
import { ReferencesCheckboxesComponent } from '../references/references-checkboxes.component';
import { ReferencesEditorComponent } from '../references/references-editor.component';
import { ReferencesRadioButtonsComponent } from '../references/references-radio-buttons.component';
import { ReferencesTagsComponent } from '../references/references-tags.component';
import { ArrayEditorComponent } from './array-editor.component';
import { AssetsEditorComponent } from './assets-editor.component';
@ -50,6 +51,7 @@ import { StockPhotoEditorComponent } from './stock-photo-editor.component';
ReferenceInputComponent,
ReferencesCheckboxesComponent,
ReferencesEditorComponent,
ReferencesRadioButtonsComponent,
ReferencesTagsComponent,
RichEditorComponent,
StarsComponent,

8
frontend/src/app/features/content/shared/forms/iframe-editor.component.ts

@ -126,6 +126,10 @@ export class IFrameEditorComponent extends StatefulComponent<State> implements O
this.sendLanguage();
}
if (changes.context) {
this.sendContext();
}
if (changes.formControlBinding) {
this.subscriptions.unsubscribeAll();
@ -300,6 +304,10 @@ export class IFrameEditorComponent extends StatefulComponent<State> implements O
this.sendMessage('expandedChanged', { expanded: this.isExpanded });
}
private sendContext() {
this.sendMessage('contextChanged', { context: this.context });
}
private sendDisabled() {
this.sendMessage('disabled', { isDisabled: this.isDisabled });
}

12
frontend/src/app/features/content/shared/references/references-checkboxes.component.ts

@ -6,9 +6,8 @@
*/
import { booleanAttribute, ChangeDetectionStrategy, Component, forwardRef, inject, Input } from '@angular/core';
import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { CheckboxGroupComponent } from '@app/shared';
import { AppsState, ContentDto, ContentsService, LanguageDto, LocalizerService, StatefulControlComponent, Subscriptions, TypedSimpleChanges, UIOptions } from '@app/shared/internal';
import { FormControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { AppsState, CheckboxGroupComponent, ContentDto, ContentsService, LanguageDto, LocalizerService, StatefulControlComponent, Subscriptions, TypedSimpleChanges, UIOptions } from '@app/shared';
import { ReferencesTagsConverter } from './references-tag-converter';
export const SQX_REFERENCES_CHECKBOXES_CONTROL_VALUE_ACCESSOR: any = {
@ -37,7 +36,7 @@ const NO_EMIT = { emitEvent: false };
ReactiveFormsModule,
],
})
export class ReferencesCheckboxesComponent extends StatefulControlComponent<State, ReadonlyArray<string>> {
export class ReferencesCheckboxesComponent extends StatefulControlComponent<State, ReadonlyArray<string> | null | undefined> {
private readonly subscriptions = new Subscriptions();
private readonly itemCount: number = inject(UIOptions).value.referencesDropdownItemCount;
private contentItems: ReadonlyArray<ContentDto> | null = null;
@ -53,7 +52,7 @@ export class ReferencesCheckboxesComponent extends StatefulControlComponent<Stat
this.setDisabledState(value === true);
}
public control = new UntypedFormControl([]);
public control = new FormControl<ReadonlyArray<string> | null | undefined>([]);
public get isValid() {
return !!this.schemaId && !!this.language;
@ -68,7 +67,7 @@ export class ReferencesCheckboxesComponent extends StatefulControlComponent<Stat
this.subscriptions.add(
this.control.valueChanges
.subscribe((value: string[]) => {
.subscribe(value => {
if (value && value.length > 0) {
this.callTouched();
this.callChange(value);
@ -125,7 +124,6 @@ export class ReferencesCheckboxesComponent extends StatefulControlComponent<Stat
this.onDisabled(!success || this.snapshot.isDisabled);
let converter: ReferencesTagsConverter;
if (success) {
converter = new ReferencesTagsConverter(this.language, this.contentItems!, this.localizer);
} else {

1
frontend/src/app/features/content/shared/references/references-radio-buttons.component.html

@ -0,0 +1 @@
<sqx-radio-group [formControl]="control" [values]="snapshot.converter.tags"></sqx-radio-group>

2
frontend/src/app/features/content/shared/references/references-radio-buttons.component.scss

@ -0,0 +1,2 @@
@import 'mixins';
@import 'vars';

135
frontend/src/app/features/content/shared/references/references-radio-buttons.component.ts

@ -0,0 +1,135 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { booleanAttribute, ChangeDetectionStrategy, Component, forwardRef, inject, Input } from '@angular/core';
import { FormControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { AppsState, ContentDto, ContentsService, LanguageDto, LocalizerService, RadioGroupComponent, StatefulControlComponent, Subscriptions, TypedSimpleChanges, UIOptions } from '@app/shared';
import { ReferencesTagsConverter } from './references-tag-converter';
export const SQX_REFERENCES_RADIO_BUTTONS_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferencesRadioButtonsComponent), multi: true,
};
interface State {
// The tags converter.
converter: ReferencesTagsConverter;
}
const NO_EMIT = { emitEvent: false };
@Component({
standalone: true,
selector: 'sqx-references-radio-buttons',
styleUrls: ['./references-radio-buttons.component.scss'],
templateUrl: './references-radio-buttons.component.html',
providers: [
SQX_REFERENCES_RADIO_BUTTONS_CONTROL_VALUE_ACCESSOR,
],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
FormsModule,
RadioGroupComponent,
ReactiveFormsModule,
],
})
export class ReferencesRadioButtonsComponent extends StatefulControlComponent<State, ReadonlyArray<string> | null | undefined> {
private readonly subscriptions = new Subscriptions();
private readonly itemCount: number = inject(UIOptions).value.referencesDropdownItemCount;
private contentItems: ReadonlyArray<ContentDto> | null = null;
@Input({ required: true })
public schemaId: string | undefined | null;
@Input({ required: true })
public language!: LanguageDto;
@Input({ transform: booleanAttribute })
public set disabled(value: boolean | undefined | null) {
this.setDisabledState(value === true);
}
public control = new FormControl<string | null | undefined>(undefined);
public get isValid() {
return !!this.schemaId && !!this.language;
}
constructor(
private readonly appsState: AppsState,
private readonly contentsService: ContentsService,
private readonly localizer: LocalizerService,
) {
super({ converter: new ReferencesTagsConverter(null!, [], localizer) });
this.subscriptions.add(
this.control.valueChanges
.subscribe(value => {
if (value) {
this.callTouched();
this.callChange([value]);
} else {
this.callTouched();
this.callChange(null);
}
}));
}
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.schemaId) {
this.resetState();
if (this.isValid) {
this.contentsService.getContents(this.appsState.appName, this.schemaId!, { take: this.itemCount })
.subscribe({
next: contents => {
this.contentItems = contents.items;
this.resetConverterState();
},
error: () => {
this.contentItems = null;
this.resetConverterState();
},
});
} else {
this.contentItems = null;
this.resetConverterState();
}
} else {
this.resetConverterState();
}
}
public onDisabled(isDisabled: boolean) {
if (isDisabled) {
this.control.disable(NO_EMIT);
} else if (this.isValid) {
this.control.enable(NO_EMIT);
}
}
public writeValue(obj: ReadonlyArray<string> | null | undefined) {
this.control.setValue(obj?.[0], NO_EMIT);
}
private resetConverterState() {
const success = this.isValid && this.contentItems && this.contentItems.length > 0;
this.onDisabled(!success || this.snapshot.isDisabled);
let converter: ReferencesTagsConverter;
if (success) {
converter = new ReferencesTagsConverter(this.language, this.contentItems!, this.localizer);
} else {
converter = new ReferencesTagsConverter(null!, [], this.localizer);
}
this.next({ converter });
}
}

2
frontend/src/app/features/schemas/pages/schemas/schemas-page.component.html

@ -34,7 +34,7 @@
<ng-container>
<div cdkDropListGroup>
@for (category of categories | async; track category.name) {
@for (category of categories | async; track category.displayName) {
<sqx-schema-category (remove)="removeCategory($event)" [schemaCategory]="category"></sqx-schema-category>
}
</div>

2
frontend/src/app/shared/components/schema-category.component.html

@ -87,7 +87,7 @@
</div>
<div class="categories" [hidden]="isCollapsed">
@for (category of schemaCategory.categories; track category.name) {
@for (category of schemaCategory.categories; track category.displayName) {
<sqx-schema-category
(remove)="remove.emit($event)"
[schemaCategory]="category"

3
frontend/src/app/shared/services/schemas.types.ts

@ -379,7 +379,7 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
}
}
export type ReferencesFieldEditor = 'List' | 'Dropdown' | 'Checkboxes' | 'Tags' | 'Input';
export type ReferencesFieldEditor = 'List' | 'Dropdown' | 'Checkboxes' | 'Tags' | 'Input' | 'Radio';
export const REFERENCES_FIELD_EDITORS: ReadonlyArray<ReferencesFieldEditor> = [
'List',
@ -387,6 +387,7 @@ export const REFERENCES_FIELD_EDITORS: ReadonlyArray<ReferencesFieldEditor> = [
'Checkboxes',
'Tags',
'Input',
'Radio',
];
export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {

4
frontend/src/app/shared/state/schemas.state.ts

@ -447,7 +447,7 @@ export function getCategoryTree(allSchemas: ReadonlyArray<SchemaDto>, categories
schemasFiltered: [],
countSchemasInSubtree: 0,
countSchemasInSubtreeFiltered: 0,
categories: [],
categories: []
};
const components: SchemaCategory = {
@ -456,7 +456,7 @@ export function getCategoryTree(allSchemas: ReadonlyArray<SchemaDto>, categories
schemasFiltered: [],
countSchemasInSubtree: 0,
countSchemasInSubtreeFiltered: 0,
categories: [],
categories: []
};
const categoryCache: { [name: string]: SchemaCategory } = {};

Loading…
Cancel
Save