Skip to content

Commit

Permalink
Merge branch 'master' into feat/1055_notifications-final
Browse files Browse the repository at this point in the history
  • Loading branch information
sleidig authored Jan 8, 2025
2 parents fdd6a31 + bc68ea4 commit 2492e64
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ <h2 mat-dialog-title i18n>Configure Text Block</h2>
<div class="entity-form-cell">
<mat-form-field>
<textarea formControlName="label" matInput rows="3"></textarea>
<mat-hint i18n>
You can use Markdown syntax to format this text. For more details,
check out the
<a href="https://www.markdownguide.org/basic-syntax" target="_blank"
>Markdown Guide</a
>
</mat-hint>
</mat-form-field>
</div>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe("BasicAutocompleteComponent", () => {
component.options = [school1, school2, school3];
let currentAutocompleteSuggestions: TestEntity[];
component.autocompleteSuggestedOptions.subscribe(
(value) => (currentAutocompleteSuggestions = value.map((o) => o.asValue)),
(value) => (currentAutocompleteSuggestions = value.map((o) => o.initial)),
);

component.autocompleteForm.setValue("");
Expand All @@ -94,6 +94,29 @@ describe("BasicAutocompleteComponent", () => {
expect(component.displayText).toBe("First Child");
});

it("should use _id and _label as default display/value for options", async () => {
const option1 = { _id: "1", _label: "First" };
const option2 = { _id: "2", _label: "Second" };
component.options = [option1, option2];

component.ngOnChanges({ options: true });
fixture.detectChanges();

// @ts-ignore
expect(component._options).toEqual([
jasmine.objectContaining({
asValue: "1",
asString: "First",
initial: option1,
}),
jasmine.objectContaining({
asValue: "2",
asString: "Second",
initial: option2,
}),
]);
});

it("should have the correct entity selected when it's name is entered", () => {
const child1 = TestEntity.create("First Child");
const child2 = TestEntity.create("Second Child");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,22 @@ export class BasicAutocompleteComponent<O, V = O>
@ViewChild(CdkVirtualScrollViewport)
virtualScrollViewport: CdkVirtualScrollViewport;

@Input() valueMapper = (option: O) => option as unknown as V;
@Input() optionToString = (option: O) => option?.toString();
@Input() valueMapper = (option: O) =>
option?.["_id"] ?? (option as unknown as V);
@Input() optionToString = (option: O) =>
option?.["_label"] ?? option?.toString();
@Input() createOption: (input: string) => Promise<O>;
@Input() hideOption: (option: O) => boolean = () => false;

/**
* Whether the user should be able to select multiple values.
*/
@Input() multi?: boolean;
@Input() reorder?: boolean;

/**
* Whether the user can manually drag & drop to reorder the selected items
*/
@Input() reorder?: boolean;

autocompleteOptions: SelectableOption<O, V>[] = [];
autocompleteForm = new FormControl("");
Expand Down Expand Up @@ -154,6 +156,14 @@ export class BasicAutocompleteComponent<O, V = O>
this.stateChanges.next();
}

/**
* The options to display in the autocomplete dropdown.
* If you pass complex objects here, you can customize what value is displayed and what value is output/stored
* by overriding the `valueMapper` and `optionToString` methods via inputs.
* By default, the "_id" property is used as the value and the "_label" property or `toString()` method as the display value.
*
* @param options Array of available options (can be filtered further by the `hideOption` function)
*/
@Input() set options(options: O[]) {
this._options = options.map((o) => this.toSelectableOption(o));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { filter, take } from "rxjs/operators";
import { Subscription } from "rxjs";
import { Entity } from "../../entity/model/entity";
import { ConfigService } from "../../config/config.service";
import { Logging } from "../../logging/logging.service";

/**
* This service handles the user session.
Expand Down Expand Up @@ -193,6 +194,7 @@ export class SessionManagerService {
// Old database is available and can be used by the current user
window.localStorage.setItem(this.DEPRECATED_DB_KEY, user.name);
this.initDatabase(environment.DB_NAME);
Logging.warn('Using deprecated "app" database name for user', user.name);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
show all fields below each other not in multiple columns, however.
<br />
</div>
<app-admin-entity-form
[config]="publicFormConfig"
(configChange)="updateValue($event)"
[entityType]="entityConstructor"
[isDisabled]="formControl.disabled"
>
</app-admin-entity-form>
<div class="coloumn-container">
<app-admin-entity-form
[config]="formConfig"
(configChange)="updateValue($event)"
[entityType]="entityConstructor"
[isDisabled]="formControl.disabled"
>
</app-admin-entity-form>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
.public-form-detail-hint-banner{
margin-bottom: sizes.$regular;
}
.coloumn-container{
overflow: hidden;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testi
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { EntityMapperService } from "app/core/entity/entity-mapper/entity-mapper.service";
import { MockEntityMapperService } from "app/core/entity/entity-mapper/mock-entity-mapper-service";
import { FieldGroup } from "../../../core/entity-details/form/field-group";

describe("EditPublicFormColumnsComponent", () => {
let component: EditPublicFormColumnsComponent;
Expand All @@ -23,6 +24,13 @@ describe("EditPublicFormColumnsComponent", () => {
fields: ["name", "phone"],
},
];

const oldColumnConfig: string[][] = [["name", "gender"], ["other"]];
const newColumnConfig: FieldGroup[] = [
{ fields: ["name", "gender"], header: null },
{ fields: ["other"], header: null },
];

beforeEach(() => {
let mockDatabase: jasmine.SpyObj<Database>;
mockEntityFormService = jasmine.createSpyObj("EntityFormService", [
Expand Down Expand Up @@ -61,4 +69,12 @@ describe("EditPublicFormColumnsComponent", () => {
it("should create the component", () => {
expect(component).toBeTruthy();
});

it("should migrate old columns config to new columns config", () => {
component.formControl.setValue(oldColumnConfig as any);

component.ngOnInit();

expect(component.formConfig.fieldGroups).toEqual(newColumnConfig);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { FormConfig } from "app/core/entity-details/form/form.component";
import { FieldGroup } from "app/core/entity-details/form/field-group";
import { AdminEntityService } from "app/core/admin/admin-entity.service";
import { EntitySchemaField } from "app/core/entity/schema/entity-schema-field";
import { PublicFormConfig } from "../public-form-config";
import { migratePublicFormConfig } from "../public-form.component";

@Component({
selector: "app-edit-public-form-columns",
Expand All @@ -23,7 +25,7 @@ export class EditPublicFormColumnsComponent
implements OnInit
{
entityConstructor: EntityConstructor;
publicFormConfig: FormConfig;
formConfig: FormConfig;
private originalEntitySchemaFields: [string, EntitySchemaField][];

private entities = inject(EntityRegistry);
Expand All @@ -33,10 +35,12 @@ export class EditPublicFormColumnsComponent
if (this.entity) {
this.entityConstructor = this.entities.get(this.entity["entity"]);

this.publicFormConfig = { fieldGroups: this.formControl.getRawValue() };
this.formControl.valueChanges.subscribe(
(v) => (this.publicFormConfig = { fieldGroups: v }),
);
const publicFormConfig: PublicFormConfig = migratePublicFormConfig({
columns: this.formControl.getRawValue(),
} as Partial<PublicFormConfig> as PublicFormConfig);
this.formConfig = {
fieldGroups: publicFormConfig.columns,
};
}

this.originalEntitySchemaFields = JSON.parse(
Expand All @@ -58,7 +62,6 @@ export class EditPublicFormColumnsComponent

updateValue(newConfig: FormConfig) {
// setTimeout needed for change detection of disabling tabs
// TODO: change logic to instead disable tabs upon edit mode immediately (without waiting for changes)
setTimeout(() => this.formControl.setValue(newConfig.fieldGroups));
this.formControl.markAsDirty();
}
Expand Down
9 changes: 7 additions & 2 deletions src/app/features/public-form/public-form.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ <h1 i18n>No access to public form "{{ formConfig?.title }}"</h1>
} @else {
<mat-card>
<mat-card-header>
<div>
<div class="card-container">
<mat-card-title>{{ formConfig?.title }}</mat-card-title>
<div class="public-form-container">
@if (formConfig?.logo) {
Expand All @@ -38,7 +38,12 @@ <h1 i18n>No access to public form "{{ formConfig?.title }}"</h1>
></app-display-img>
}
</div>
<mat-card-subtitle>{{ formConfig?.description }}</mat-card-subtitle>
@if (formConfig?.description) {
<!-- needs the @if wrapper because <markdown> doesn't seem to do good change detection -->
<mat-card-subtitle>
<markdown>{{ formConfig?.description }}</markdown>
</mat-card-subtitle>
}
</div>
</mat-card-header>

Expand Down
8 changes: 6 additions & 2 deletions src/app/features/public-form/public-form.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ button {
}

.public-form-container{
text-align: center;
display: flex;
justify-content: center;
}

.card-container{
width: 100%;
}

.public-form-image {
max-width: 100%;
height: 100px;
object-fit: contain;
}

.error-container {
Expand Down
99 changes: 53 additions & 46 deletions src/app/features/public-form/public-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { DefaultValueConfig } from "../../core/entity/schema/default-value-confi
import { DisplayImgComponent } from "../file/display-img/display-img.component";
import { EntityAbility } from "app/core/permissions/ability/entity-ability";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
import { MarkdownPageModule } from "../markdown-page/markdown-page.module";

@UntilDestroy()
@Component({
Expand All @@ -34,6 +35,7 @@ import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
MatCardModule,
DisplayImgComponent,
FontAwesomeModule,
MarkdownPageModule,
],
standalone: true,
})
Expand Down Expand Up @@ -107,56 +109,11 @@ export class PublicFormComponent<E extends Entity> implements OnInit {
this.error = "no_permissions";
return;
}
this.formConfig = this.migratePublicFormConfig(this.formConfig);
this.formConfig = migratePublicFormConfig(this.formConfig);
this.fieldGroups = this.formConfig.columns;
await this.initForm();
}

private migratePublicFormConfig(
formConfig: PublicFormConfig,
): PublicFormConfig {
if (formConfig.columns) {
formConfig.columns = formConfig.columns.map(
(column: FieldGroup | string[]) => ({
fields: Array.isArray(column) ? column : column.fields || [],
}),
);
}

for (let [id, value] of Object.entries(formConfig["prefilled"] ?? [])) {
const defaultValue: DefaultValueConfig = { mode: "static", value };

const field: FormFieldConfig = this.findFieldInFieldGroups(id);
if (!field) {
// add new field to last column
const lastColumn: FieldGroup =
formConfig.columns[formConfig.columns.length - 1];
lastColumn.fields.push({ id, defaultValue, hideFromForm: true });
} else {
field.defaultValue = defaultValue;
}
}
delete formConfig.prefilled;

return formConfig;
}

private findFieldInFieldGroups(id: string): FormFieldConfig {
for (const column of this.formConfig.columns) {
for (const field of column.fields) {
if (typeof field === "string" && field === id) {
// replace the string with a field object, so that we can pass by reference
const newField: FormFieldConfig = { id: field };
column.fields[column.fields.indexOf(field)] = newField;
return newField;
} else if (typeof field === "object" && field.id === id) {
return field;
}
}
}
return undefined;
}

private async initForm() {
this.entity = new this.entityType();
this.form = await this.entityFormService.createEntityForm(
Expand All @@ -165,3 +122,53 @@ export class PublicFormComponent<E extends Entity> implements OnInit {
);
}
}

export function migratePublicFormConfig(
formConfig: PublicFormConfig,
): PublicFormConfig {
if (formConfig.columns) {
formConfig.columns = formConfig.columns.map(
(column: FieldGroup | string[]) => {
return Array.isArray(column)
? { fields: column, header: null }
: { fields: column.fields || [], header: column?.header || null };
},
);
}

for (let [id, value] of Object.entries(formConfig["prefilled"] ?? [])) {
const defaultValue: DefaultValueConfig = { mode: "static", value };

const field: FormFieldConfig = findFieldInFieldGroups(formConfig, id);
if (!field) {
// add new field to last column
const lastColumn: FieldGroup =
formConfig.columns[formConfig.columns.length - 1];
lastColumn.fields.push({ id, defaultValue, hideFromForm: true });
} else {
field.defaultValue = defaultValue;
}
}
delete formConfig.prefilled;

return formConfig;
}

function findFieldInFieldGroups(
formConfig: PublicFormConfig,
id: string,
): FormFieldConfig {
for (const column of formConfig.columns) {
for (const field of column.fields) {
if (typeof field === "string" && field === id) {
// replace the string with a field object, so that we can pass by reference
const newField: FormFieldConfig = { id: field };
column.fields[column.fields.indexOf(field)] = newField;
return newField;
} else if (typeof field === "object" && field.id === id) {
return field;
}
}
}
return undefined;
}
3 changes: 2 additions & 1 deletion src/app/features/public-form/public-form.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ const viewConfigs: ViewConfig[] = [
{
id: "permissions_remark",
editComponent: "EditDescriptionOnly",
label: $localize`:PublicFormConfig admin form:If you want external people filling this form without logging in, the Permission System also has to allow \"public\" users to create new records of this type. If you are seeing problems submitting the form, please contact your technical support team.`,
label: $localize`:PublicFormConfig admin form:If you want external people filling this form without logging in, the _Permission System_ also has to allow **"public"** users to create new records of this type.<br>
If you are seeing problems submitting the form, please contact your **technical support team**.`,
},
"entity",
"description",
Expand Down

0 comments on commit 2492e64

Please sign in to comment.