diff --git a/demo/src/app/app.component.html b/demo/src/app/app.component.html
index 592a1c1646..61b7dcad22 100644
--- a/demo/src/app/app.component.html
+++ b/demo/src/app/app.component.html
@@ -48,6 +48,7 @@
{{title}}
Directions
Time filter
OGC filter
+ Edition
diff --git a/demo/src/app/app.module.ts b/demo/src/app/app.module.ts
index c85e1d0611..504c66b4e8 100644
--- a/demo/src/app/app.module.ts
+++ b/demo/src/app/app.module.ts
@@ -41,6 +41,7 @@ import { AppPrintModule } from './geo/print/print.module';
import { AppDirectionsModule } from './geo/directions/directions.module';
import { AppTimeFilterModule } from './geo/time-filter/time-filter.module';
import { AppOgcFilterModule } from './geo/ogc-filter/ogc-filter.module';
+import { AppEditionModule } from './geo/edition/edition.module';
import { AppContextModule } from './context/context/context.module';
@@ -90,6 +91,7 @@ import { AppComponent } from './app.component';
AppDirectionsModule,
AppTimeFilterModule,
AppOgcFilterModule,
+ AppEditionModule,
AppContextModule,
diff --git a/demo/src/app/common/entity-selector/entity-selector.component.html b/demo/src/app/common/entity-selector/entity-selector.component.html
index 471d4076d3..27b4cde822 100644
--- a/demo/src/app/common/entity-selector/entity-selector.component.html
+++ b/demo/src/app/common/entity-selector/entity-selector.component.html
@@ -2,8 +2,9 @@
Common
Entity Selector
- See the code of this example
+ See the code of this example
+
+ Geo
+ Edition
+
+ See the code of this example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/src/app/geo/edition/edition.component.scss b/demo/src/app/geo/edition/edition.component.scss
new file mode 100644
index 0000000000..0a808cfb8a
--- /dev/null
+++ b/demo/src/app/geo/edition/edition.component.scss
@@ -0,0 +1,17 @@
+igo-map-browser {
+ width: 100%;
+ height: 300px;
+}
+
+igo-panel {
+ width: 100%;
+ padding-top: 10px;
+}
+
+igo-entity-table {
+ height: 400px;
+}
+
+igo-actionbar {
+ height: 48px;
+}
diff --git a/demo/src/app/geo/edition/edition.component.ts b/demo/src/app/geo/edition/edition.component.ts
new file mode 100644
index 0000000000..264ef485fb
--- /dev/null
+++ b/demo/src/app/geo/edition/edition.component.ts
@@ -0,0 +1,106 @@
+import { Component, OnInit } from '@angular/core';
+
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+import { LanguageService } from '@igo2/core';
+import {
+ ActionbarMode,
+ EntityRecord,
+ EntityTableScrollBehavior,
+ Editor,
+ EditorStore
+} from '@igo2/common';
+import {
+ IgoMap,
+ DataSourceService,
+ LayerService,
+ LayerOptions,
+ WFSDataSourceOptions
+} from '@igo2/geo';
+
+@Component({
+ selector: 'app-edition',
+ templateUrl: './edition.component.html',
+ styleUrls: ['./edition.component.scss']
+})
+export class AppEditionComponent implements OnInit {
+
+ public map = new IgoMap({
+ controls: {
+ attribution: {
+ collapsed: true
+ }
+ }
+ });
+
+ public view = {
+ center: [-72, 47.2],
+ zoom: 5
+ };
+
+ public editorStore = new EditorStore([]);
+
+ public selectedEditor$: Observable;
+
+ public actionbarMode = ActionbarMode.Dock;
+
+ public scrollBehavior = EntityTableScrollBehavior.Instant;
+
+ constructor(
+ private languageService: LanguageService,
+ private dataSourceService: DataSourceService,
+ private layerService: LayerService
+ ) {}
+
+ ngOnInit() {
+ this.selectedEditor$ = this.editorStore.stateView
+ .firstBy$((record: EntityRecord) => record.state.selected === true)
+ .pipe(
+ map((record: EntityRecord) => {
+ return record === undefined ? undefined : record.entity;
+ })
+ );
+
+ this.dataSourceService
+ .createAsyncDataSource({
+ type: 'osm'
+ })
+ .subscribe(dataSource => {
+ this.map.addLayer(
+ this.layerService.createLayer({
+ title: 'OSM',
+ source: dataSource
+ })
+ );
+ });
+
+ const wfsDatasource: WFSDataSourceOptions = {
+ type: 'wfs',
+ url: 'https://geoegl.msp.gouv.qc.ca/apis/ws/swtq',
+ params: {
+ featureTypes: 'etablissement_mtq',
+ fieldNameGeometry: 'geometry',
+ version: '2.0.0',
+ outputFormat: 'geojson'
+ },
+ sourceFields: [
+ {name: 'idetablis', alias: 'ID'},
+ {name: 'nometablis', alias: 'Name'},
+ {name: 'typetablis', alias: 'Type'}
+ ]
+ };
+
+ this.dataSourceService
+ .createAsyncDataSource(wfsDatasource)
+ .subscribe(dataSource => {
+ const layer: LayerOptions = {
+ title: 'Simple WFS ',
+ visible: true,
+ source: dataSource
+ };
+ this.map.addLayer(this.layerService.createLayer(layer));
+ });
+ }
+
+}
diff --git a/demo/src/app/geo/edition/edition.module.ts b/demo/src/app/geo/edition/edition.module.ts
new file mode 100644
index 0000000000..7d1a85c6e0
--- /dev/null
+++ b/demo/src/app/geo/edition/edition.module.ts
@@ -0,0 +1,40 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import {
+ MatCardModule,
+ MatButtonModule,
+ MatIconModule
+} from '@angular/material';
+
+import {
+ IgoActionModule,
+ IgoEntityModule,
+ IgoEditionModule,
+ IgoPanelModule
+} from '@igo2/common';
+import {
+ IgoMapModule,
+ IgoGeoEditionModule
+} from '@igo2/geo';
+
+import { AppEditionComponent } from './edition.component';
+import { AppEditionRoutingModule } from './edition-routing.module';
+
+@NgModule({
+ declarations: [AppEditionComponent],
+ imports: [
+ CommonModule,
+ AppEditionRoutingModule,
+ MatCardModule,
+ MatButtonModule,
+ MatIconModule,
+ IgoActionModule,
+ IgoEntityModule,
+ IgoEditionModule,
+ IgoPanelModule,
+ IgoMapModule,
+ IgoGeoEditionModule
+ ],
+ exports: [AppEditionComponent]
+})
+export class AppEditionModule {}
diff --git a/demo/src/app/geo/feature/feature.component.ts b/demo/src/app/geo/feature/feature.component.ts
index 506d75f82d..ef5a8707b3 100644
--- a/demo/src/app/geo/feature/feature.component.ts
+++ b/demo/src/app/geo/feature/feature.component.ts
@@ -61,7 +61,7 @@ export class AppFeatureComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
- const loadingStrategy = new FeatureStoreLoadingStrategy();
+ const loadingStrategy = new FeatureStoreLoadingStrategy({});
this.store.addStrategy(loadingStrategy);
const selectionStrategy = new FeatureStoreSelectionStrategy({
diff --git a/packages/common/src/lib/edition/edition.module.ts b/packages/common/src/lib/edition/edition.module.ts
new file mode 100644
index 0000000000..d259a46fda
--- /dev/null
+++ b/packages/common/src/lib/edition/edition.module.ts
@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { IgoEditorOutletModule } from './editor-outlet/editor-outlet.module';
+import { IgoEditorSelectorModule } from './editor-selector/editor-selector.module';
+
+@NgModule({
+ imports: [
+ CommonModule
+ ],
+ exports: [
+ IgoEditorOutletModule,
+ IgoEditorSelectorModule
+ ],
+ declarations: []
+})
+export class IgoEditionModule {}
diff --git a/packages/common/src/lib/edition/editor-outlet/editor-outlet.component.html b/packages/common/src/lib/edition/editor-outlet/editor-outlet.component.html
new file mode 100644
index 0000000000..b5a3be5209
--- /dev/null
+++ b/packages/common/src/lib/edition/editor-outlet/editor-outlet.component.html
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/packages/common/src/lib/edition/editor-outlet/editor-outlet.component.scss b/packages/common/src/lib/edition/editor-outlet/editor-outlet.component.scss
new file mode 100644
index 0000000000..31c5e40a86
--- /dev/null
+++ b/packages/common/src/lib/edition/editor-outlet/editor-outlet.component.scss
@@ -0,0 +1,3 @@
+igo-widget-outlet {
+ height: 100%;
+}
diff --git a/packages/common/src/lib/edition/editor-outlet/editor-outlet.component.ts b/packages/common/src/lib/edition/editor-outlet/editor-outlet.component.ts
new file mode 100644
index 0000000000..87e189989d
--- /dev/null
+++ b/packages/common/src/lib/edition/editor-outlet/editor-outlet.component.ts
@@ -0,0 +1,76 @@
+import {
+ Component,
+ Input,
+ Output,
+ EventEmitter,
+ ChangeDetectionStrategy
+} from '@angular/core';
+
+import { BehaviorSubject } from 'rxjs';
+
+import { Widget } from '../../widget';
+import { Editor } from '../shared/editor';
+
+/**
+ * This component dynamically render an Editor's active widget.
+ * It also deactivate that widget whenever the widget's component
+ * emit the 'cancel' or 'complete' event.
+ */
+@Component({
+ selector: 'igo-editor-outlet',
+ templateUrl: './editor-outlet.component.html',
+ styleUrls: ['./editor-outlet.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class EditorOutletComponent {
+
+ /**
+ * Editor
+ */
+ @Input() editor: Editor;
+
+ /**
+ * Event emitted when a widget is deactivate which happens
+ * when the widget's component emits the 'cancel' or 'complete' event.
+ */
+ @Output() deactivateWidget = new EventEmitter();
+
+ /**
+ * Observable of the editor's active widget
+ * @internal
+ */
+ get widget$(): BehaviorSubject { return this.editor.widget$; }
+
+ /**
+ * Observable of the editor's widget inputs
+ * @internal
+ */
+ get widgetInputs$(): BehaviorSubject<{ [key: string]: any }> {
+ return this.editor.widgetInputs$;
+ }
+
+ constructor() {}
+
+ /**
+ * When a widget's component emit the 'cancel' event,
+ * deactivate that widget and emit the 'deactivateWidget' event.
+ * @param widget Widget
+ * @internal
+ */
+ onWidgetCancel(widget: Widget) {
+ this.editor.deactivateWidget();
+ this.deactivateWidget.emit(widget);
+ }
+
+ /**
+ * When a widget's component emit the 'cancel' event,
+ * deactivate that widget and emit the 'deactivateWidget' event.
+ * @param widget Widget
+ * @internal
+ */
+ onWidgetComplete(widget: Widget) {
+ this.editor.deactivateWidget();
+ this.deactivateWidget.emit(widget);
+ }
+
+}
diff --git a/packages/common/src/lib/edition/editor-outlet/editor-outlet.module.ts b/packages/common/src/lib/edition/editor-outlet/editor-outlet.module.ts
new file mode 100644
index 0000000000..dc4430c87f
--- /dev/null
+++ b/packages/common/src/lib/edition/editor-outlet/editor-outlet.module.ts
@@ -0,0 +1,23 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { IgoWidgetOutletModule } from '../../widget/widget-outlet/widget-outlet.module';
+
+import { EditorOutletComponent } from './editor-outlet.component';
+
+/**
+ * @ignore
+ */
+@NgModule({
+ imports: [
+ CommonModule,
+ IgoWidgetOutletModule
+ ],
+ exports: [
+ EditorOutletComponent
+ ],
+ declarations: [
+ EditorOutletComponent
+ ]
+})
+export class IgoEditorOutletModule {}
diff --git a/packages/common/src/lib/edition/editor-selector/editor-selector.component.html b/packages/common/src/lib/edition/editor-selector/editor-selector.component.html
new file mode 100644
index 0000000000..abce91769b
--- /dev/null
+++ b/packages/common/src/lib/edition/editor-selector/editor-selector.component.html
@@ -0,0 +1,5 @@
+
+
diff --git a/packages/common/src/lib/edition/editor-selector/editor-selector.component.scss b/packages/common/src/lib/edition/editor-selector/editor-selector.component.scss
new file mode 100644
index 0000000000..c520d95b59
--- /dev/null
+++ b/packages/common/src/lib/edition/editor-selector/editor-selector.component.scss
@@ -0,0 +1,9 @@
+igo-entity-selector ::ng-deep mat-form-field {
+ .mat-form-field-infix {
+ padding: 0;
+ }
+
+ .mat-form-field-wrapper {
+ padding-bottom: 1.75em;
+ }
+}
diff --git a/packages/common/src/lib/edition/editor-selector/editor-selector.component.ts b/packages/common/src/lib/edition/editor-selector/editor-selector.component.ts
new file mode 100644
index 0000000000..0111df452a
--- /dev/null
+++ b/packages/common/src/lib/edition/editor-selector/editor-selector.component.ts
@@ -0,0 +1,56 @@
+import {
+ Component,
+ Input,
+ Output,
+ EventEmitter,
+ ChangeDetectionStrategy
+} from '@angular/core';
+
+import { getEntityTitle } from '../../entity';
+import { Editor } from '../shared/editor';
+import { EditorStore } from '../shared/store';
+
+/**
+ * Drop list that activates the selected editor emit an event.
+ */
+@Component({
+ selector: 'igo-editor-selector',
+ templateUrl: './editor-selector.component.html',
+ styleUrls: ['./editor-selector.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class EditorSelectorComponent {
+
+ /**
+ * Store that holds the available editors.
+ */
+ @Input() store: EditorStore;
+
+ /**
+ * Event emitted when an editor is selected or unselected
+ */
+ @Output() selectedChange = new EventEmitter<{
+ selected: boolean;
+ entity: Editor;
+ }>();
+
+ /**
+ * @internal
+ */
+ getEditorTitle(editor: Editor): string {
+ return getEntityTitle(editor);
+ }
+
+ /**
+ * When an editor is manually selected, select it into the
+ * store and emit an event.
+ * @internal
+ * @param event The selection change event
+ */
+ onSelectedChange(event: {entity: Editor}) {
+ const editor = event.entity;
+ this.store.activateEditor(editor);
+ this.selectedChange.emit({selected: true, entity: editor});
+ }
+
+}
diff --git a/packages/common/src/lib/edition/editor-selector/editor-selector.module.ts b/packages/common/src/lib/edition/editor-selector/editor-selector.module.ts
new file mode 100644
index 0000000000..5d6a4fc98f
--- /dev/null
+++ b/packages/common/src/lib/edition/editor-selector/editor-selector.module.ts
@@ -0,0 +1,23 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { IgoEntitySelectorModule } from '../../entity/entity-selector/entity-selector.module';
+
+import { EditorSelectorComponent } from './editor-selector.component';
+
+/**
+ * @ignore
+ */
+@NgModule({
+ imports: [
+ CommonModule,
+ IgoEntitySelectorModule
+ ],
+ exports: [
+ EditorSelectorComponent
+ ],
+ declarations: [
+ EditorSelectorComponent
+ ]
+})
+export class IgoEditorSelectorModule {}
diff --git a/packages/common/src/lib/edition/index.ts b/packages/common/src/lib/edition/index.ts
new file mode 100644
index 0000000000..c3da79f741
--- /dev/null
+++ b/packages/common/src/lib/edition/index.ts
@@ -0,0 +1 @@
+export * from './shared';
diff --git a/packages/common/src/lib/edition/shared/edition.interfaces.ts b/packages/common/src/lib/edition/shared/edition.interfaces.ts
new file mode 100644
index 0000000000..fdb10546a0
--- /dev/null
+++ b/packages/common/src/lib/edition/shared/edition.interfaces.ts
@@ -0,0 +1,10 @@
+import { ActionStore } from '../../action';
+import { EntityStore, EntityTableTemplate } from '../../entity';
+
+export interface EditorConfig {
+ id: string;
+ title: string;
+ tableTemplate?: EntityTableTemplate;
+ entityStore?: EntityStore