Skip to content

Commit

Permalink
Merge pull request #1747 from CentreForDigitalHumanities/feature/reor…
Browse files Browse the repository at this point in the history
…der-fields

Feature/reorder fields
  • Loading branch information
lukavdplas authored Feb 4, 2025
2 parents 79197a4 + fbe9607 commit 6b7d5b8
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 9 deletions.
16 changes: 15 additions & 1 deletion backend/addcorpus/json_corpora/tests/test_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from addcorpus.models import Field, Corpus
from addcorpus.serializers import CorpusJSONDefinitionSerializer
from addcorpus.models import Corpus, CorpusConfiguration
from addcorpus.json_corpora.export_json import export_json_corpus

def test_json_corpus_import(db, json_mock_corpus, json_corpus_definition):
json_mock_corpus.delete()
Expand Down Expand Up @@ -75,6 +76,20 @@ def test_serializer_update(db, json_corpus_definition, json_mock_corpus: Corpus)
serializer.update(json_mock_corpus, serializer.validated_data)
assert Field.objects.filter(corpus_configuration__corpus=json_mock_corpus).count() == 1

def test_serializer_update_field_order(db, json_corpus_definition, json_mock_corpus: Corpus):
# send corpus with reverse field order
data = {
'definition': json_corpus_definition,
'active': True,
}
data['definition']['fields'] = list(reversed(data['definition']['fields']))
serializer = CorpusJSONDefinitionSerializer(data=data)
assert serializer.is_valid()
serializer.update(json_mock_corpus, serializer.validated_data)

json_mock_corpus.refresh_from_db()
assert export_json_corpus(json_mock_corpus) == data['definition']


def test_parse_content_field(content_field_json):
data = _parse_field(content_field_json)
Expand Down Expand Up @@ -194,4 +209,3 @@ def test_parse_geo_field(geo_field_json):
assert field.hidden == False
assert field.sortable == False
assert field.searchable == False

9 changes: 6 additions & 3 deletions backend/addcorpus/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,10 @@ def create(self, validated_data: Dict):

corpus = Corpus.objects.create(**definition_data)
configuration = CorpusConfiguration.objects.create(corpus=corpus, **configuration_data)
for field_data in fields_data:
Field.objects.create(corpus_configuration=configuration, **field_data)
for i, field_data in enumerate(fields_data):
Field.objects.create(
corpus_configuration=configuration, position=i, **field_data
)

if validated_data.get('active') == True:
corpus.active = True
Expand All @@ -198,7 +200,7 @@ def update(self, instance: Corpus, validated_data: Dict):
setattr(configuration, attr, configuration_data[attr])
configuration.save()

for field_data in fields_data:
for i, field_data in enumerate(fields_data):
try:
field = Field.objects.get(
corpus_configuration=configuration, name=field_data['name'])
Expand All @@ -207,6 +209,7 @@ def update(self, instance: Corpus, validated_data: Dict):
name=field_data['name'])
for attr in field_data:
setattr(field, attr, field_data[attr])
field.position = i
field.save()

configuration.fields.exclude(name__in=(f['name'] for f in fields_data)).delete()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<h1 class="title">Fields</h1>
<form class="container is-readable" [formGroup]="fieldsForm" (ngSubmit)="onSubmit()">
<ng-container formArrayName="fields">
<div class="box" *ngFor="let field of fields.controls; let i=index" [formGroupName]="i">
<div class="box"
*ngFor="let field of fields.controls;
index as i; first as isFirst; last as isLast;
trackBy: fieldControlName"
[formGroupName]="i">
<div class="field">
<label class="label" for="column_name">Column name</label>
<div class="control">
Expand Down Expand Up @@ -71,14 +75,41 @@ <h1 class="title">Fields</h1>
</div>
</div>

<div class="field is-grouped" style="justify-content: end;">
<div class="control">
<!-- the first "move down" and the last "move up" button are disabled;
this is done with CSS/ARIA/javascript rather than the "disabled"
attribute, so the control can stay focused when the user moves
the field to the first/last position.
-->
<button class="button" type="button" aria-label="move up"
[id]="moveControlID(i, field, -1)"
iaBalloon="move field up"
[attr.aria-disabled]="isFirst"
[class.is-disabled]="isFirst"
(click)="isFirst || moveField(i, field, -1)">
<fa-icon [icon]="directionIcons.up" aria-hidden="true" />
</button>
</div>
<div class="control">
<button class="button" type="button" aria-label="move down"
[id]="moveControlID(i, field, +1)"
iaBalloon="move field down"
[attr.aria-disabled]="isLast"
[class.is-disabled]="isLast"
(click)="isLast || moveField(i, field, +1)">
<fa-icon [icon]="directionIcons.down" aria-hidden="true" />
</button>
</div>
</div>
</div>
</ng-container>



<div class="field">
<p class="control">
<button class="button is-primary">Save changes</button>
<button class="button is-primary" type="submit">Save changes</button>
</p>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input, SimpleChanges } from '@angular/core';
import { Component, ElementRef, Input, SimpleChanges } from '@angular/core';
import { FormArray, FormControl, FormGroup } from '@angular/forms';
import {
APICorpusDefinitionField,
Expand All @@ -9,6 +9,7 @@ import { Subject, takeUntil } from 'rxjs';
import * as _ from 'lodash';

import { ISO6393Languages } from '../constants';
import { actionIcons, directionIcons } from '@shared/icons';

@Component({
selector: 'ia-field-form',
Expand Down Expand Up @@ -50,7 +51,12 @@ export class FieldFormComponent {

languageOptions = ISO6393Languages;

constructor() {}
actionIcons = actionIcons;
directionIcons = directionIcons;

constructor(
private el: ElementRef<HTMLElement>,
) {}

get fields(): FormArray {
return this.fieldsForm.get('fields') as FormArray;
Expand Down Expand Up @@ -112,8 +118,34 @@ export class FieldFormComponent {
this.corpus.definition.fields =
newFields as CorpusDefinition['definition']['fields'];
this.corpus.save().subscribe({
next: console.log,
error: console.error,
});
}

/** identifier for a field control
*
* includes the index as an argument so this can be used as a TrackByFunction
*/
fieldControlName(index: number, field: FormControl) {
return field.get('extract').get('column').value as string;
}

moveField(index: number, field: FormControl, delta: number): void {
this.fields.removeAt(index);
this.fields.insert(index + delta, field);

// after change detection, restore focus to the button
setTimeout(() => this.focusOnMoveControl(index, field, delta));
}

moveControlID(index: number, field: FormControl, delta: number): string {
const label = delta > 0 ? 'movedown' : 'moveup';
return label + '-' + this.fieldControlName(index, field);
}

focusOnMoveControl(index: number, field: FormControl, delta: number): void {
const selector = '#' + this.moveControlID(index, field, delta);
const button = this.el.nativeElement.querySelector<HTMLButtonElement>(selector);
button.focus();
}
}
9 changes: 9 additions & 0 deletions frontend/src/app/shared/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import {
faUser,
faArrowsUpDown,
faArrowRightArrowLeft,
faArrowUp,
faArrowDown,
} from '@fortawesome/free-solid-svg-icons';

type IconDefinition = SolidIconDefinition | RegularIconDefinition;
Expand All @@ -83,6 +85,13 @@ export const navIcons: Icons = {
tags: faTags,
};

export const directionIcons: Icons = {
up: faArrowUp,
down: faArrowDown,
left: faArrowLeft,
right: faArrowRight,
};

export const actionIcons: Icons = {
search: faSearch,
help: faInfoCircle,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,7 @@ a.dropdown-item[disabled] {
.entity-miscellaneous {
@include mark-entity($entity-miscellaneous);
}

.is-disabled {
@extend [disabled];
}

0 comments on commit 6b7d5b8

Please sign in to comment.