Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Edition Workspace #1675

Draft
wants to merge 47 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a04d927
init new edition workspace.
olivierSaintCyr Apr 9, 2024
8f1801e
feat(edition): create table template factory
olivierSaintCyr Apr 9, 2024
26e80a6
feat(edition); add workspace to layer
olivierSaintCyr Apr 9, 2024
8105c76
add edition-workspace to edition actions
olivierSaintCyr Apr 9, 2024
f42a853
fix(edition-workspace): not in resolution
olivierSaintCyr Apr 10, 2024
584908d
refactor(edition-workspace): inResolutionRange
olivierSaintCyr Apr 10, 2024
674ddf5
feat(edition-workspace): impl table edition
olivierSaintCyr Apr 10, 2024
d8173e4
feat(edition workspace): cancel edition
olivierSaintCyr Apr 10, 2024
f59d2cb
refactor(edition-workspace): table template factory
olivierSaintCyr Apr 10, 2024
a0014bd
feat(edition-workspace): edit feature attributes
olivierSaintCyr Apr 10, 2024
cb16025
fix(edition): loading not updating
olivierSaintCyr Apr 16, 2024
9183689
feat(rest-api-edition): impl update body
olivierSaintCyr Apr 16, 2024
0248e75
feat(edition-workspace): delete
olivierSaintCyr Apr 16, 2024
9ecd084
feat(edition-workspace): create feature edition & cancel
olivierSaintCyr Apr 16, 2024
eff3f0f
feat(edition-workspace): create feature without geometry
olivierSaintCyr Apr 17, 2024
dd01bf4
adapt edtion
olivierSaintCyr Apr 17, 2024
4437495
fix build
olivierSaintCyr Apr 17, 2024
faa6b47
fix build
olivierSaintCyr Apr 17, 2024
95e2cb0
fix(edition-workspace): add when edit not canceling edit
olivierSaintCyr Apr 17, 2024
0f56edb
fix(edition-workspace): update not sending data
olivierSaintCyr Apr 17, 2024
e8e43b1
feat(edition-workspace): message when success and error
olivierSaintCyr Apr 17, 2024
3c6a40c
Merge branch 'next' into f/edition-wfs-ogcapi-datasource
olivierSaintCyr Apr 17, 2024
4c05fa6
refactor(edition-workspace): feature creation
olivierSaintCyr Apr 19, 2024
ecbc533
feat(edition-workspace): enable disable geo edition
olivierSaintCyr Apr 19, 2024
c84a6ba
feat(workspace-editor): add point on map
olivierSaintCyr Apr 19, 2024
972eecc
refactor(edition-workspace): add Feature type to Workspace
olivierSaintCyr Apr 23, 2024
c320437
fix(layer-workspace): add Feature type to all layer-workspace
olivierSaintCyr Apr 23, 2024
1022f25
refactor(new-edition-workspace): limit export scope to only new-editi…
olivierSaintCyr Apr 23, 2024
7595fbd
refactor(edition-ws-table-template-factory): add typing todo
olivierSaintCyr Apr 23, 2024
f127837
refactor(EditionOptions)!: add values for modifyMethod
olivierSaintCyr Apr 23, 2024
2709180
refactor(layer-workspace): rename layer-workspace to any-workspace
olivierSaintCyr Apr 23, 2024
306720c
feat(edition-ws-geo-editor): impl edit new point
olivierSaintCyr Apr 23, 2024
34d0f15
fix(ws-geo-editor): interaction not disapearing
olivierSaintCyr Apr 23, 2024
6646558
feat(edition-ws): edit existing feature geometry
olivierSaintCyr Apr 23, 2024
9dde428
remove done todo
olivierSaintCyr Apr 24, 2024
1285c68
fix(ws-edition-geo-edit): modify 2x feature not clearing old geometry
olivierSaintCyr Apr 24, 2024
f27582f
test(edition-workspace-factory)
olivierSaintCyr Apr 24, 2024
7b499c6
test(edition-ws-factory): refactor map creation
olivierSaintCyr Apr 24, 2024
f881459
test(new-edition-workspace): test create feature happy path
olivierSaintCyr Apr 24, 2024
8066015
refactor(new-edition-workspace): remove useless line
olivierSaintCyr Apr 25, 2024
413c78b
test(new-edition-ws): update feature happy path & creation unhappy path
olivierSaintCyr Apr 25, 2024
fce3f50
test(new-edition-ws): update unhappy path
olivierSaintCyr Apr 25, 2024
a58111f
fix(new-edition-ws): cancel didn't completely reset feature
olivierSaintCyr Apr 25, 2024
c631aff
test(new-edition-ws): cancel feature update
olivierSaintCyr Apr 25, 2024
13e36e2
test(new-edition-ws): delete feature tests
olivierSaintCyr Apr 25, 2024
dfc4cd6
test(rest-api-edition): test body
olivierSaintCyr Apr 25, 2024
641f446
Merge branch 'edition-workspace-wfs-tests' into edition-workspace-wfs
olivierSaintCyr Apr 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export interface EditionOptions {
messages?: any[];
addHeaders?: { [key: string]: any };
modifyHeaders?: { [key: string]: any };
modifyProtocol?: string;
modifyMethod?: 'post' | 'patch';
addButton?: boolean;
modifyButton?: boolean;
deleteButton?: boolean;
Expand All @@ -104,7 +104,6 @@ export interface SourceFieldsValidationParams {
maxLength?: number;
minLength?: number;
readonly?: boolean;
send?: boolean;
}

export interface Legend {
Expand Down
1 change: 1 addition & 0 deletions packages/geo/src/lib/workspace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './workspace-selector/workspace-selector.module';
export * from './workspace-updator/workspace-updator.directive';
export * from './workspace-updator/workspace-updator.module';
export * from './widgets/index';
export * from './new-edition-workspace/new-edition-workspace';
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { EntityTableButton, EntityTableColumnRenderer } from '@igo2/common';

import olFeature from 'ol/Feature';

import { first, skipWhile } from 'rxjs';

import { RelationOptions, SourceFieldsOptionsParams } from '../../datasource';
import { VectorLayer } from '../../layer/shared';
import { EditionFeature, NewEditionWorkspace } from './new-edition-workspace';

export class EditionWorkspaceTableTemplateFactory {
// TODO add columns type see EntityTableColumn
addTemplateToWorkspace(workspace: NewEditionWorkspace, layer: VectorLayer) {
const buttons = this.getButtonTemplate(workspace, layer);

const fields = layer.dataSource.options.sourceFields;
if (!fields) {
return this.createEmptySourceFieldTemplate(workspace);
}

const sourceFieldsColumns = this.getSourceFieldsTemplate(fields);

const relations = layer.dataSource.options.relations ?? [];
const relationsColumns = this.getRelationsTemplate(relations);

const columns: unknown[] = sourceFieldsColumns;
columns.push(...relationsColumns);
columns.push(...buttons);
workspace.meta.tableTemplate = {
selection: false,
sort: true,
columns
};
}

private createEmptySourceFieldTemplate(workspace: NewEditionWorkspace) {
workspace.entityStore.entities$
.pipe(
skipWhile((val) => val.length === 0),
first()
)
.subscribe((entities) => {
const ol = entities[0].ol as olFeature;
const columnsFromFeatures = ol
.getKeys()
.filter(
(col) =>
!col.startsWith('_') &&
col !== 'geometry' &&
col !== ol.getGeometryName() &&
!col.match(/boundedby/gi)
)
.map((key) => {
return {
name: `properties.${key}`,
title: key,
renderer: EntityTableColumnRenderer.UnsanitizedHTML
};
});
workspace.meta.tableTemplate = {
selection: false,
sort: true,
columns: columnsFromFeatures
};
});
}

private getRelationsTemplate(relations: RelationOptions[]) {
return relations.map((relation: RelationOptions) => {
return {
name: `properties.${relation.name}`,
title: relation.alias ? relation.alias : relation.name,
renderer: EntityTableColumnRenderer.Icon,
icon: relation.icon,
parent: relation.parent,
type: 'relation',
tooltip: relation.tooltip,
onClick: () => {
throw Error('Not yet implemented');
// if (this.adding$.getValue() === false) {
// this.ws$.next(relation.title);
// }
},
cellClassFunc: () => {
return { class_icon: true };
}
};
});
}

private getSourceFieldsTemplate(fields: SourceFieldsOptionsParams[]) {
// TODO implement domainValues
// if (field.type === 'list' || field.type === 'autocomplete') {

return fields.map((field) => {
return {
name: `properties.${field.name}`,
title: field.alias ? field.alias : field.name,
renderer: EntityTableColumnRenderer.UnsanitizedHTML,
valueAccessor: undefined,
cellClassFunc: () => {
const cellClass = {};
if (field.type) {
cellClass[`class_${field.type}`] = true;
return cellClass;
}
},
primary: field.primary === true ? true : false,
visible: field.visible,
validation: field.validation,
linkColumnForce: field.linkColumnForce,
type: field.type,
domainValues: undefined,
relation: undefined,
multiple: field.multiple,
step: field.step,
tooltip: field.tooltip
};
});
}

private getButtonTemplate(
workspace: NewEditionWorkspace,
layer: VectorLayer
) {
const confirmButton = {
editMode: true,
icon: 'check',
color: 'primary',
disabled: false,
click: (feature: EditionFeature) => {
workspace.saveFeature(feature);
}
};

const cancelButton = {
editMode: true,
icon: 'alpha-x',
color: 'primary',
disabled: false,
click: (feature: EditionFeature) => {
workspace.cancelEdit(feature);
}
};

workspace.isLoading$.subscribe((isLoading) => {
confirmButton.disabled = isLoading;
cancelButton.disabled = isLoading;
});

return [
{
name: 'edition',
title: undefined,
renderer: EntityTableColumnRenderer.ButtonGroup,
primary: false,
valueAccessor: () => {
return [
{
editMode: false,
icon: 'pencil',
color: 'primary',
disabled:
layer.dataSource.options.edition.modifyButton === false
? true
: false,
click: (feature: EditionFeature) => {
workspace.updateFeature(feature);
}
},
{
editMode: false,
icon: 'delete',
color: 'warn',
disabled:
layer.dataSource.options.edition.deleteButton === false
? true
: false,
click: (feature: EditionFeature) => {
workspace.deleteFeature(feature);
}
},
confirmButton,
cancelButton
] as EntityTableButton[];
}
}
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { MatDialogModule } from '@angular/material/dialog';

import { ConfigService } from '@igo2/core/config';
import { MessageService } from '@igo2/core/message';
import { StorageService } from '@igo2/core/storage';
import {
DataSourceOptions,
FeatureDataSource,
NewEditionWorkspace
} from '@igo2/geo';

import { VectorLayer } from '../../layer/shared';
import { IgoMap } from '../../map/shared';
import { EditionWorkspaceFactoryService } from './edition-workspace-factory.service';
import { createTestIgoMap } from './tests.utils';

describe('EditionWorkspaceFactoryService', () => {
let service: EditionWorkspaceFactoryService;
let map: IgoMap;

const dataSourceOptions: DataSourceOptions = {
type: 'wfs',
edition: {
enabled: true,
baseUrl: 'http://testing.com',
addUrl: '/',
modifyUrl: '/',
deleteUrl: '/',
geomType: 'Point',
hasGeometry: true
}
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, MatDialogModule],
providers: [
{
provide: StorageService,
useValue: jasmine.createSpyObj(StorageService, ['get'])
},
{
provide: ConfigService,
useValue: jasmine.createSpyObj(ConfigService, ['getConfig'])
},
{ provide: MessageService, useValue: {} }
]
});
map = createTestIgoMap();
map.init();
service = TestBed.inject(EditionWorkspaceFactoryService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});

it('should create a WFSEditionWorkspace', () => {
const layer = new VectorLayer({
source: new FeatureDataSource(dataSourceOptions)
});
map.addLayer(layer);
const workspace = service.createWFSEditionWorkspace(layer, map);
expect(workspace).toBeInstanceOf(NewEditionWorkspace);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';

import { ActionStore, EntityStoreFilterSelectionStrategy } from '@igo2/common';
import { ConfigService } from '@igo2/core/config';
import { MessageService } from '@igo2/core/message';
import { StorageService } from '@igo2/core/storage';

import { FeatureDataSource } from '../../datasource/shared/datasources/feature-datasource';
import {
FeatureMotion,
FeatureStore,
FeatureStoreInMapExtentStrategy,
FeatureStoreInMapResolutionStrategy,
FeatureStoreLoadingLayerStrategy,
FeatureStoreSelectionStrategy
} from '../../feature/shared';
import {
GeoWorkspaceOptions,
ImageLayer,
VectorLayer
} from '../../layer/shared';
import { IgoMap } from '../../map/shared';
import { createFilterInMapExtentOrResolutionStrategy } from '../shared/workspace.utils';
import { EditionWorkspaceTableTemplateFactory } from './edition-table-template-factory';
import { NewEditionWorkspace } from './new-edition-workspace';
import { RestAPIEdition } from './rest-api-edition';

@Injectable({
providedIn: 'root'
})
export class EditionWorkspaceFactoryService {
get zoomAuto(): boolean {
return this.storageService.get('zoomAuto') as boolean;
}

private tableTemplateFactory = new EditionWorkspaceTableTemplateFactory();

constructor(
private http: HttpClient,
private storageService: StorageService,
private configService: ConfigService,
private messageService: MessageService,
private dialog: MatDialog
) {}

createWFSEditionWorkspace(
layer: VectorLayer,
map: IgoMap
): NewEditionWorkspace {
const workspace = new RestAPIEdition(
this.http,
this.dialog,
this.messageService,
{
id: layer.id,
title: layer.title,
layer,
map,
editionUrl: this.getEditionUrl(layer),
entityStore: this.createFeatureStore(layer, map),
actionStore: new ActionStore([]),
meta: {
tableTemplate: undefined
}
}
);

this.tableTemplateFactory.addTemplateToWorkspace(workspace, layer);
layer.options.workspace = Object.assign({}, layer.options.workspace, {
srcId: layer.id,
workspaceId: layer.id,
enabled: true
} as GeoWorkspaceOptions);
return workspace;
}

private createFeatureStore(layer: VectorLayer, map: IgoMap): FeatureStore {
const store = new FeatureStore([], { map });
store.bindLayer(layer);

const loadingStrategy = new FeatureStoreLoadingLayerStrategy({});
const inMapExtentStrategy = new FeatureStoreInMapExtentStrategy({});
const inMapResolutionStrategy = new FeatureStoreInMapResolutionStrategy({});
const selectedRecordStrategy = new EntityStoreFilterSelectionStrategy({});
const selectionStrategy = new FeatureStoreSelectionStrategy({
layer: new VectorLayer({
zIndex: 300,
source: new FeatureDataSource(),
style: undefined,
showInLayerList: false,
exportable: false,
browsable: false
}),
map,
hitTolerance: 15,
motion: this.zoomAuto ? FeatureMotion.Default : FeatureMotion.None,
many: true,
dragBox: true
}); // TODO check if usefull
store.addStrategy(loadingStrategy, true);
store.addStrategy(inMapExtentStrategy, true);
store.addStrategy(inMapResolutionStrategy, true);
store.addStrategy(selectionStrategy, true);
store.addStrategy(selectedRecordStrategy, false);
store.addStrategy(createFilterInMapExtentOrResolutionStrategy(), true);
return store;
}

private getEditionUrl(layer: VectorLayer | ImageLayer): string {
const editionUrl = layer.dataSource.options.edition.baseUrl;
const baseUrl = this.configService.getConfig('edition.url');
return !baseUrl ? editionUrl : new URL(editionUrl, baseUrl).href;
}
}
Loading
Loading