Skip to content

Commit 4f17c80

Browse files
Add toggle for entity map view (#1429)
1 parent a82facb commit 4f17c80

File tree

21 files changed

+673
-252
lines changed

21 files changed

+673
-252
lines changed

src/components/entity/list.vue

Lines changed: 80 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ except according to the terms contained in the LICENSE file.
1919
<entity-filters v-model:conflict="conflict" v-model:creatorId="creatorIds" v-model:creationDate="creationDateRange"
2020
:disabled="deleted" :disabled-message="deleted ? $t('filterDisabledMessage') : null" @reset-click="resetFilters"/>
2121
</form>
22+
<radio-field v-if="dataset.dataExists && dataset.hasGeometry"
23+
v-model="dataView" :options="viewOptions" :disabled="deleted"
24+
:disabled-message="$t('mapDisabled')" button-appearance/>
2225
<teleport-if-exists v-if="odataEntities.dataExists" to=".dataset-entities-heading-row">
2326
<entity-download-button :odata-filter="deleted ? null : odataFilter"
2427
:search-term="deleted ? null : searchTerm"
@@ -27,30 +30,21 @@ except according to the terms contained in the LICENSE file.
2730
</teleport-if-exists>
2831
</div>
2932
<table-refresh-bar :odata="odataEntities"
30-
:refreshing="refreshing" @refresh-click="fetchChunk(false, true)"/>
31-
<entity-table v-show="odataEntities.dataExists" ref="table"
32-
v-model:all-selected="allSelected"
33-
:properties="dataset.properties" :deleted="deleted"
34-
:awaiting-deleted-responses="awaitingResponses"
35-
@selection-changed="handleSelectionChange"
36-
@update="showUpdate"
37-
@resolve="showResolve" @delete="showDelete" @restore="showRestore"/>
38-
39-
<p v-show="emptyTableMessage" class="empty-table-message">
40-
{{ emptyTableMessage }}
33+
:refreshing="refreshing" @refresh-click="refresh"/>
34+
<p v-show="emptyMessage" class="empty-table-message">
35+
{{ emptyMessage }}
4136
</p>
42-
<odata-loading-message :state="odataEntities.initiallyLoading"
43-
type="entity"
44-
:top="pagination.size"
45-
:filter="odataFilter != null || !!searchTerm"
46-
:total-count="dataset.dataExists ? dataset.entities : 0"/>
47-
48-
<!-- @update:page is emitted on size change as well -->
49-
<pagination v-if="pagination.count > 0"
50-
v-model:page="pagination.page" v-model:size="pagination.size"
51-
:count="pagination.count" :size-options="pageSizeOptions"
52-
:spinner="odataEntities.awaitingResponse"
53-
@update:page="handlePageChange()"/>
37+
38+
<entity-table-view v-if="dataView === 'table'" ref="view"
39+
v-model:all-selected="allSelected" :deleted="deleted"
40+
:filter="odataFilter" :search-term="searchTerm"
41+
:awaiting-responses="awaitingResponses"
42+
@selection-changed="handleSelectionChange"
43+
@clear-selection="clearSelectedEntities"
44+
@update="showUpdate" @resolve="showResolve" @delete="showDelete"
45+
@restore="showRestore"/>
46+
<entity-map-view v-else ref="view" :filter="geojsonFilter"
47+
:search-term="searchTerm" :awaiting-responses="awaitingResponses"/>
5448
</disable-container>
5549

5650
<entity-update v-bind="update" @hide="hideUpdate" @success="afterUpdate"/>
@@ -75,28 +69,30 @@ except according to the terms contained in the LICENSE file.
7569
</template>
7670

7771
<script>
78-
import { reactive, watch } from 'vue';
72+
import { watch } from 'vue';
7973
74+
import ActionBar from '../action-bar.vue';
75+
import DisableContainer from '../disable-container.vue';
8076
import EntityDownloadButton from './download-button.vue';
8177
import EntityDelete from './delete.vue';
78+
import EntityMapView from './map-view.vue';
8279
import EntityRestore from './restore.vue';
8380
import EntityFilters from './filters.vue';
84-
import EntityTable from './table.vue';
81+
import EntityTableView from './table-view.vue';
8582
import EntityUpdate from './update.vue';
8683
import EntityResolve from './resolve.vue';
87-
import OdataLoadingMessage from '../odata-loading-message.vue';
88-
import Spinner from '../spinner.vue';
89-
import Pagination from '../pagination.vue';
84+
import RadioField from '../radio-field.vue';
85+
import TableRefreshBar from '../table-refresh-bar.vue';
9086
import TeleportIfExists from '../teleport-if-exists.vue';
9187
import SearchTextbox from '../search-textbox.vue';
92-
import ActionBar from '../action-bar.vue';
93-
import DisableContainer from '../disable-container.vue';
94-
import TableRefreshBar from '../table-refresh-bar.vue';
88+
import Spinner from '../spinner.vue';
9589
90+
import useDataView from '../../composables/data-view';
9691
import useQueryRef from '../../composables/query-ref';
9792
import useDateRangeQueryRef from '../../composables/date-range-query-ref';
9893
import useRequest from '../../composables/request';
9994
import { apiPaths, requestAlertMessage } from '../../util/request';
95+
import { joinSentences } from '../../util/i18n';
10096
import { modalData } from '../../util/reactivity';
10197
import { noop } from '../../util/util';
10298
import { odataEntityToRest } from '../../util/odata';
@@ -110,13 +106,13 @@ export default {
110106
DisableContainer,
111107
EntityDelete,
112108
EntityDownloadButton,
113-
EntityRestore,
114109
EntityFilters,
110+
EntityMapView,
115111
EntityResolve,
116-
EntityTable,
112+
EntityRestore,
113+
EntityTableView,
117114
EntityUpdate,
118-
OdataLoadingMessage,
119-
Pagination,
115+
RadioField,
120116
SearchTextbox,
121117
Spinner,
122118
TableRefreshBar,
@@ -182,15 +178,15 @@ export default {
182178
});
183179
184180
const creationDateRange = useDateRangeQueryRef();
181+
const { dataView, options: viewOptions } = useDataView();
185182
186183
const { request } = useRequest();
187184
188-
const pageSizeOptions = [250, 500, 1000];
189-
190185
return {
191-
dataset, odataEntities, conflict, request,
192-
deletedEntityCount, pageSizeOptions, searchTerm, entityCreators, creatorIds,
193-
creationDateRange
186+
dataset, deletedEntityCount, odataEntities, entityCreators,
187+
searchTerm, creatorIds, creationDateRange, conflict,
188+
dataView, viewOptions,
189+
request
194190
};
195191
},
196192
data() {
@@ -213,8 +209,6 @@ export default {
213209
214210
awaitingResponses: new Set(),
215211
216-
pagination: { page: 0, size: this.pageSizeOptions[0], count: 0 },
217-
now: new Date().toISOString(),
218212
snapshotFilter: '',
219213
// used for restoring them back when undo button is pressed
220214
bulkDeletedEntities: [],
@@ -251,35 +245,55 @@ export default {
251245
}
252246
return conditions.length !== 0 ? conditions.join(' and ') : null;
253247
},
254-
emptyTableMessage() {
248+
geojsonFilter() {
249+
const query = {};
250+
if (this.filtersOnCreatorId) query.creatorId = this.creatorIds;
251+
if (this.creationDateRange.length !== 0) {
252+
query.start__gte = this.creationDateRange[0].toISO();
253+
query.end__lte = this.creationDateRange[1].endOf('day').toISO();
254+
}
255+
if (this.conflict.length === 1)
256+
query.conflict = this.conflict[0] ? ['soft', 'hard'] : 'null';
257+
return Object.keys(query).length !== 0 ? query : null;
258+
},
259+
emptyMessage() {
255260
if (!this.odataEntities.dataExists) return '';
256261
if (this.odataEntities.value.length > 0) return '';
257262
263+
// Cases related to entity deletion
258264
if (this.odataEntities.removedEntities.size === this.odataEntities.count && this.odataEntities.count > 0) {
259265
return this.deleted ? this.$t('deletedEntity.allRestored') : this.$t('allDeleted');
260266
}
261267
if (this.odataEntities.removedEntities.size > 0 && this.odataEntities.value.length === 0) {
262268
return this.deleted ? this.$t('deletedEntity.allRestoredOnPage') : this.$t('allDeletedOnPage');
263269
}
264-
return this.deleted ? this.$t('deletedEntity.emptyTable')
265-
: (this.odataFilter ? this.$t('noMatching') : this.$t('noEntities'));
270+
if (this.deleted) {
271+
return this.$t('deletedEntity.emptyTable');
272+
}
273+
274+
if (this.odataFilter) return this.$t('noMatching');
275+
return this.dataView === 'table'
276+
? this.$t('noEntities')
277+
: joinSentences(this.$i18n, [
278+
this.$t('common.emptyMap'),
279+
this.$t('emptyMap')
280+
]);
266281
},
267282
actionBarState() {
268283
return this.selectedEntities.size > 0 && !this.alert.state && !this.container.openModal.state;
269284
}
270285
},
271286
watch: {
272-
deleted() {
273-
this.fetchChunk(true);
274-
},
275287
'odataEntities.value': {
276288
handler() {
277289
this.clearSelectedEntities();
278290
}
279291
},
280292
'odataEntities.count': {
281293
handler() {
282-
if (this.dataset.dataExists && this.odataEntities.dataExists && !this.odataFilter && !this.deleted && !this.searchTerm)
294+
if (this.dataset.dataExists && this.odataEntities.dataExists &&
295+
this.dataView === 'table' && !this.odataFilter && !this.deleted &&
296+
!this.searchTerm)
283297
this.dataset.entities = this.odataEntities.count;
284298
}
285299
},
@@ -309,87 +323,24 @@ export default {
309323
310324
},
311325
created() {
312-
this.fetchChunk(true);
313-
this.$watch(() => [this.odataFilter, this.searchTerm], () => this.fetchChunk(true));
314326
this.fetchCreators();
315327
},
316328
methods: {
317-
// `clear` indicates whether this.odataEntities should be cleared before
318-
// sending the request. `refresh` indicates whether the request is a
319-
// background refresh (whether the refresh button was pressed).
320-
fetchChunk(clear, refresh = false) {
321-
this.refreshing = refresh;
322-
// Are we fetching the first chunk of entities or the next chunk?
323-
const first = clear || refresh;
324-
325-
if (first) {
326-
this.now = new Date().toISOString();
327-
this.setSnapshotFilter();
328-
this.pagination.page = 0;
329-
}
330-
331-
let $filter = this.snapshotFilter;
332-
if (this.odataFilter) {
333-
$filter += ` and ${this.odataFilter}`;
334-
}
335-
336-
const $search = this.searchTerm ? this.searchTerm : undefined;
337-
338-
this.clearSelectedEntities();
339-
340-
this.odataEntities.request({
341-
url: apiPaths.odataEntities(
342-
this.projectId,
343-
this.datasetName,
344-
{
345-
$top: this.pagination.size,
346-
$skip: this.pagination.page * this.pagination.size,
347-
$count: true,
348-
$search,
349-
$filter,
350-
$orderby: '__system/createdAt desc'
351-
}
352-
),
353-
clear,
354-
patch: !first
355-
? (response) => this.odataEntities.replaceData(response.data, response.config)
356-
: null
357-
})
358-
.then(() => {
359-
this.pagination.count = this.odataEntities.count;
360-
361-
if (this.deleted) {
362-
this.deletedEntityCount.cancelRequest();
363-
if (!this.deletedEntityCount.dataExists) {
364-
this.deletedEntityCount.data = reactive({});
365-
}
366-
this.deletedEntityCount.value = this.odataEntities.count;
367-
}
368-
})
369-
.finally(() => { this.refreshing = false; })
370-
.catch(noop);
371-
372-
// emit event to parent component to re-fetch deleted Entity count
373-
if (refresh && !this.deleted) {
374-
this.$emit('fetch-deleted-count');
375-
}
376-
},
377-
setSnapshotFilter() {
378-
this.snapshotFilter = '';
379-
if (this.deleted) {
380-
this.snapshotFilter += `__system/deletedAt le ${this.now}`;
381-
} else {
382-
this.snapshotFilter += `__system/createdAt le ${this.now} and `;
383-
this.snapshotFilter += `(__system/deletedAt eq null or __system/deletedAt gt ${this.now})`;
384-
}
385-
},
386329
resetFilters() {
387330
this.$router.replace({ path: this.$route.path, query: {} });
388331
},
332+
refresh() {
333+
this.refreshing = true;
334+
this.$refs.view.fetchData(false)
335+
.then(() => { this.refreshing = false; });
336+
337+
// emit event to parent component to re-fetch deleted Entity count
338+
if (!this.deleted) this.$emit('fetch-deleted-count');
339+
},
389340
// This method is called directly by DatasetEntities.
390341
reset() {
391342
if (this.odataFilter == null && !this.searchTerm) {
392-
this.fetchChunk(true);
343+
this.$refs.view.fetchData();
393344
} else {
394345
this.resetFilters();
395346
}
@@ -437,7 +388,7 @@ export default {
437388
this.odataEntities.value[index] = newOData;
438389
439390
if (this.resolveIndex == null)
440-
this.$refs.table.afterUpdate(index);
391+
this.$refs.view.afterUpdate(index);
441392
else
442393
this.showResolve(this.resolveIndex);
443394
},
@@ -466,7 +417,7 @@ export default {
466417
};
467418
this.odataEntities.value[this.resolveIndex] = newOData;
468419
469-
this.$refs.table.afterUpdate(this.resolveIndex);
420+
this.$refs.view.afterUpdate(this.resolveIndex);
470421
},
471422
showDelete(entity) {
472423
this.deleteModal.show({ entity });
@@ -503,7 +454,7 @@ export default {
503454
? this.odataEntities.value.findIndex(entity => entity.__id === uuid)
504455
: -1;
505456
if (index !== -1) {
506-
this.$refs.table.afterDelete(index);
457+
this.$refs.view.afterDelete(index);
507458
this.selectedEntities.delete(this.odataEntities.value[index]);
508459
this.odataEntities.value.splice(index, 1);
509460
}
@@ -547,7 +498,7 @@ export default {
547498
? this.odataEntities.value.findIndex(entity => entity.__id === uuid)
548499
: -1;
549500
if (index !== -1) {
550-
this.$refs.table.afterDelete(index);
501+
this.$refs.view.afterDelete(index);
551502
this.odataEntities.value.splice(index, 1);
552503
}
553504
})
@@ -556,20 +507,14 @@ export default {
556507
this.awaitingResponses.delete(uuid);
557508
});
558509
},
559-
handlePageChange() {
560-
// This function is called for size change as well. So when the total number of entities are
561-
// less than the lowest size option, hence we don't need to make a request.
562-
if (this.odataEntities.count < this.pageSizeOptions[0]) return;
563-
this.fetchChunk(false);
564-
},
565510
clearSelectedEntities() {
566511
this.selectedEntities.clear();
567512
this.odataEntities.value?.forEach(e => { e.__system.selected = false; });
568513
this.allSelected = false;
569514
},
570515
cancelBackgroundRefresh() {
571516
if (!this.refreshing) return;
572-
this.odataEntities.cancelRequest();
517+
this.$refs.view.cancelFetch();
573518
this.deletedEntityCount.cancelRequest();
574519
},
575520
requestBulkDelete() {
@@ -696,9 +641,7 @@ export default {
696641
flex-wrap: wrap-reverse;
697642
}
698643
699-
#entity-list table:has(tbody:empty) {
700-
display: none;
701-
}
644+
#entity-list .radio-field { margin-left: auto; }
702645
703646
#entity-table:has(tbody tr) + .empty-table-message {
704647
display: none;
@@ -717,6 +660,7 @@ export default {
717660
// This text is shown when there are no Entities to show in a table.
718661
"noEntities": "There are no Entities to show.",
719662
"noMatching": "There are no matching Entities.",
663+
"emptyMap": "Entities only appear if they include data in the geometry property.",
720664
"allDeleted": "All Entities are deleted.",
721665
"allDeletedOnPage": "All Entities on the page have been deleted.",
722666
"alert": {
@@ -726,6 +670,7 @@ export default {
726670
},
727671
"filterDisabledMessage": "Filtering is unavailable for deleted Entities",
728672
"searchDisabledMessage": "Search is unavailable for deleted Entities",
673+
"mapDisabled": "Map is unavailable for deleted Entities",
729674
"downloadDisabled": "Download is unavailable for deleted Entities",
730675
"deletedEntity": {
731676
"emptyTable": "There are no deleted Entities.",

0 commit comments

Comments
 (0)