gh-105: Improve child entity visualization
diff --git a/src/lib/components/block/entity-view/entity-table-body.vue b/src/lib/components/block/entity-view/entity-table-body.vue
deleted file mode 100644
index 674c404..0000000
--- a/src/lib/components/block/entity-view/entity-table-body.vue
+++ /dev/null
@@ -1,152 +0,0 @@
-<template>
-<b-tbody>
- <b-tr>
- <!-- column One -->
- <b-td>
- <template>
- <b-button variant="link" size="sm" v-on:click="onClickExpand()" v-b-tooltip.hover :title="expandChild?'Colapse':'Expand'">
- <b-icon :icon="expandChild?'chevron-up':'chevron-down'"></b-icon>
- </b-button>
- </template>
- <router-link :to="`/tenants/${clientId}/entities/${entity.entityId}`" v-slot="{href, navigate}">
- <b-link :href="href" v-on:click="navigate">{{ entity.entityId }}</b-link>
- </router-link>
- <button-copy :value="entity.entityId"/>
- </b-td>
- <!-- Column Two -->
- <b-td>{{ entity.name }}</b-td>
- <!-- Column Three -->
- <b-td>{{ entity.type }}</b-td>
- <!-- Column Four -->
- <b-td>{{ entity.description }}</b-td>
- <!-- Column Five -->
- <b-td>{{ entity.createdAt }}</b-td>
- <!-- Column Six -->
- <b-td>{{ entity.updatedAt }}</b-td>
- <!-- Column Seven -->
- <b-td>
- <!-- Create Child Entity Button -->
- <router-link :to="{name:'create_entity', query:{ entityId:`${entity.entityId}`}}" v-slot="{href, navigate}" tag="">
- <b-button variant="link" size="sm" v-on:click="navigate" v-b-tooltip.hover title="Create Child Entity">
- <b-icon icon="folder-plus"></b-icon>
- <!-- <v-icon>mdi-pl?us</v-icon> -->
- </b-button>
- </router-link>
- <!-- Share Entity Button -->
- <b-button variant="link" size="sm" v-b-modal="`modal-select-users-or-groups-${entityIndex}`" v-b-tooltip.hover title="Share">
- <span>{{ entity.sharedCount }}</span>
- <b-icon icon="share"></b-icon>
- </b-button>
- <!-- Modal Share Entity -->
- <modal-share-entity
- :client-id="clientId" :entity-id="entity.entityId"
- :modal-id="`modal-select-users-or-groups-${entityIndex}`"
- :title="`Share Entity '${entity.name}'`"
- v-on:close="refreshData"
- />
- <!-- Delete Entity Button -->
- <button-overlay :show="processingDelete[entity.entityId]">
- <b-button variant="link" size="sm" v-on:click="onClickDelete(entity)" v-b-tooltip.hover title="Delete">
- <b-icon icon="trash"></b-icon>
- </b-button>
- </button-overlay>
- </b-td>
- </b-tr>
- <!-- Child Entities -->
- <template v-if="expandChild">
- <b-tr><b-td colspan="100%">
- <template v-if="!childEntities || !childEntities.length" >
- <span style="display: block; text-align: center;">No child entities to show.</span>
- </template>
- <template v-else>
- <b-table-simple>
- <b-thead style="display:none">
- <b-tr>
- <b-th>Entity ID</b-th>
- <b-th>Name</b-th>
- <b-th>Type</b-th>
- <b-th>Description</b-th>
- <b-th>Created</b-th>
- <b-th>Last Updated</b-th>
- </b-tr>
- </b-thead>
- <template v-for="(childEntity, childEntityIndex) in childEntities">
- <entity-table-body v-on:refresh-data="refreshData" :key="childEntity.entityId" :entity="childEntity" :entityIndex="entityIndex.toString()+'-'+childEntityIndex.toString()" :allEntities="allEntities"></entity-table-body>
- </template>
- </b-table-simple>
- </template>
- </b-td></b-tr>
- </template>
-</b-tbody>
-</template>
-
-<script>
-import store from "../../../store"
-import ModalShareEntity from "../../modals/modal-share-entity";
-import ButtonOverlay from "../../overlay/button-overlay";
-import ButtonCopy from "../../button/button-copy";
-
-
-export default {
- name: 'entity-table-body',
- store: store,
- components: {ButtonCopy, ButtonOverlay, ModalShareEntity},
- props: {
- entity: Object,
- entityIndex: [Number, String],
- allEntities: Array
- },
- data() {
- return {
- processingDelete: {},
- expandChild: false,
- errors: []
- }
- },
- computed: {
- clientId() {
- return this.$route.params.clientId;
- },
- childEntities() {
- return this.allEntities==undefined?this.allEntities:
- this.allEntities.filter((entity)=>{return entity.parentId==this.entity.entityId;});
- }
- },
- methods: {
- onClickExpand() {
- this.expandChild = !this.expandChild
- },
- refreshData() {
- this.$emit('refresh-data');
- },
- async onClickDelete({entityId, name, description, type, ownerId}) {
- this.processingDelete = {...this.processingDelete, [entityId]: true};
-
- try {
- await this.$store.dispatch("entity/deleteEntity", {
- clientId: this.clientId,
- entityId,
- name,
- description,
- type,
- ownerId
- });
- this.refreshData();
- } catch (error) {
- this.errors.push({
- title: `Unknown error when deleting the entity '${entityId}'.`,
- source: error, variant: "danger"
- });
- }
-
- this.processingDelete = {...this.processingDelete, [entityId]: false};
- }
- }
-}
-</script>
-
-<style scoped>
-.hidden_header{
- display: none;
-}
-</style>
\ No newline at end of file
diff --git a/src/lib/components/pages/TenantEntities.vue b/src/lib/components/pages/TenantEntities.vue
index 87df364..069b969 100644
--- a/src/lib/components/pages/TenantEntities.vue
+++ b/src/lib/components/pages/TenantEntities.vue
@@ -5,7 +5,7 @@
<b-button variant="primary" @click="navigate">Create New Entity</b-button>
</router-link>
</template>
- <table-overlay-info :rows="5" :columns="1" :data="rootEntities">
+ <table-overlay-info :rows="5" :columns="1" :data="entityIds">
<b-table-simple>
<b-thead>
<b-tr>
@@ -14,13 +14,99 @@
<b-th>Type</b-th>
<b-th>Description</b-th>
<b-th>Created</b-th>
- <b-th>Owner</b-th>
<b-th>Last Updated</b-th>
+ <b-th>Owner</b-th>
</b-tr>
</b-thead>
- <template v-for="(entity, entityIndex) in rootEntities">
- <entity-table-body v-on:refresh-data="refreshData" :key="entity.entityId" :entity="entity" :entityIndex="entityIndex" :allEntities="allEntities"></entity-table-body>
- </template>
+ <b-tbody>
+ <template v-for="(entityId, entityIndex) in entityIds">
+ <b-tr :key="entityIndex" v-if="entityChildrenExpanded[entityMap[entityId].parentId]">
+ <!-- column One -->
+ <b-td>
+ <div style="display: flex; flex-direction: row;">
+ <div :style="`padding-right: ${entityIndent[entityId] * 20}px;`"></div>
+ <div style="width: 36px;">
+ <div v-if="hasChildren(entityMap[entityId])">
+ <b-button v-if="entityChildrenExpanded[entityId]" variant="link" size="sm"
+ v-on:click="onClickCollapse(entityMap[entityId])" v-b-tooltip.hover title="Collapse">
+ <b-icon icon="chevron-down"></b-icon>
+ </b-button>
+ <b-button v-else variant="link" size="sm" v-on:click="onClickExpand(entityMap[entityId])"
+ v-b-tooltip.hover title="Collapse">
+ <b-icon icon="chevron-right"></b-icon>
+ </b-button>
+ </div>
+ </div>
+ <div style="flex: 1;">
+ <router-link :to="`/tenants/${clientId}/entities/${entityId}`" v-slot="{href, navigate}">
+ <b-link :href="href" v-on:click="navigate">{{ entityId }}</b-link>
+ </router-link>
+ </div>
+ <button-copy :value="entityId"/>
+ </div>
+ </b-td>
+ <!-- Column Two -->
+ <b-td>{{ entityMap[entityId].name }}</b-td>
+ <!-- Column Three -->
+ <b-td>{{ entityMap[entityId].type }}</b-td>
+ <!-- Column Four -->
+ <b-td>{{ entityMap[entityId].description }}</b-td>
+ <!-- Column Five -->
+ <b-td>{{ entityMap[entityId].createdAt }}</b-td>
+ <!-- Column Six -->
+ <b-td>{{ entityMap[entityId].updatedAt }}</b-td>
+ <!-- Column Seven -->
+ <b-td>{{ entityMap[entityId].ownerId }}</b-td>
+ <!-- Column Eight -->
+ <b-td>
+ <div style="display: flex; flex-direction: row;">
+ <div>
+ <!-- Create Child Entity Button -->
+ <router-link :to="{name:'create_entity', query:{ entityId:`${entityId}`}}"
+ v-slot="{href, navigate}"
+ tag="">
+ <b-button variant="link" size="sm" v-on:click="navigate" v-b-tooltip.hover
+ title="Create Child Entity">
+ <b-icon icon="folder-plus"></b-icon>
+ <!-- <v-icon>mdi-pl?us</v-icon> -->
+ </b-button>
+ </router-link>
+ </div>
+ <div>
+ <!-- Share Entity Button -->
+ <b-button variant="link" size="sm" v-b-modal="`modal-select-users-or-groups-${entityIndex}`"
+ v-b-tooltip.hover
+ title="Share">
+ <span style="display: flex; flex-direction: row;">
+ <span style="padding-right: 4px;">{{ entityMap[entityId].sharedCount }}</span>
+ <b-icon icon="share"></b-icon>
+ </span>
+ </b-button>
+ <!-- Modal Share Entity -->
+ <modal-share-entity
+ :client-id="clientId" :entity-id="entityId"
+ :modal-id="`modal-select-users-or-groups-${entityIndex}`"
+ :title="`Share Entity '${entityMap[entityId].name}'`"
+ v-on:close="refreshData"
+ />
+ </div>
+ <div>
+ <!-- Delete Entity Button -->
+ <button-overlay :show="processingDelete[entityId]">
+ <b-button variant="link" size="sm" v-on:click="onClickDelete(entityMap[entityId])"
+ v-b-tooltip.hover
+ title="Delete">
+ <b-icon icon="trash"></b-icon>
+ </b-button>
+ </button-overlay>
+ </div>
+ </div>
+
+
+ </b-td>
+ </b-tr>
+ </template>
+ </b-tbody>
</b-table-simple>
<!-- <b-pagination-->
@@ -40,17 +126,27 @@
import store from "../../store"
import TenantHome from "./TenantHome";
import TableOverlayInfo from "../overlay/table-overlay-info";
-import EntityTableBody from "../block/entity-view/entity-table-body";
+import ModalShareEntity from "@/lib/components/modals/modal-share-entity";
+import ButtonOverlay from "@/lib/components/overlay/button-overlay";
+import ButtonCopy from "@/lib/components/button/button-copy";
export default {
name: "TenantEntities",
store: store,
- components: {TableOverlayInfo, TenantHome, EntityTableBody},
+ components: {ButtonCopy, ButtonOverlay, ModalShareEntity, TableOverlayInfo, TenantHome},
data() {
return {
processingDelete: {},
- expandEntities: {},
- errors: []
+ errors: [],
+
+ entityIds: null,
+ entityMap: {},
+ entityIndent: {},
+ entityChildrenIds: {},
+ entityChildrenMapped: {},
+ entityChildrenExpanded: {
+ [""]: true
+ }
}
},
computed: {
@@ -61,9 +157,6 @@
allEntities() {
return this.$store.getters["entity/getEntities"]({clientId: this.clientId, ownerId: this.currentUsername});
},
- rootEntities() {
- return this.allEntities==undefined?this.allEntities:this.allEntities.filter((entity)=>{return !entity.parentId;});
- },
breadcrumbLinks() {
return [{to: `/tenants/${this.clientId}/entities`, name: "Entities"}];
},
@@ -71,7 +164,98 @@
return this.$store.getters["auth/currentUsername"];
}
},
+ watch: {
+ allEntities() {
+ const entityIds = [];
+ const entityMap = {};
+ const entityIndent = {};
+ const entityChildrenIds = {};
+ const entityChildrenMapped = {};
+ if (this.allEntities) {
+ for (let i = 0; i < this.allEntities.length; i++) {
+ const entity = this.allEntities[i];
+ entityMap[entity.entityId] = entity;
+ entityChildrenMapped[entity.entityId] = false;
+ if (!entityChildrenIds[entity.parentId]) {
+ entityChildrenIds[entity.parentId] = [entity.entityId];
+ } else {
+ entityChildrenIds[entity.parentId].push(entity.entityId);
+ }
+
+ if (!entity.parentId) {
+ entityIds.push(entity.entityId);
+ entityIndent[entity.entityId] = 0;
+ }
+ }
+ }
+
+ console.log("this.entityIds : ", entityIds);
+
+ this.entityIds = entityIds;
+ this.entityMap = entityMap;
+ this.entityIndent = entityIndent;
+ this.entityChildrenIds = entityChildrenIds;
+ this.entityChildrenMapped = entityChildrenMapped;
+
+ }
+ },
methods: {
+ rootEntities() {
+ return this.childEntities({entityId: null});
+ },
+ hasChildren(parentEntity) {
+ return this.entityChildrenIds[parentEntity.entityId] && this.entityChildrenIds[parentEntity.entityId].length > 0;
+ },
+ childEntities(parentEntity) {
+ return this.entities ? [] :
+ this.entities.filter((entity) => {
+ return entity.parentId === parentEntity.entityId;
+ });
+ },
+ onClickExpand({entityId}) {
+ if (!this.entityChildrenMapped[entityId]) {
+ let entityIds = [];
+ let entityIndent = {...this.entityIndent};
+ for (let i = 0; i < this.entityIds.length; i++) {
+ entityIds.push(this.entityIds[i]);
+ if (this.entityIds[i] === entityId && this.hasChildren(this.entityMap[this.entityIds[i]])) {
+ for (let j = 0; j < this.entityChildrenIds[entityId].length; j++) {
+ entityIds.push(this.entityChildrenIds[entityId][j]);
+ entityIndent[this.entityChildrenIds[entityId][j]] = entityIndent[entityId] + 1;
+ }
+ }
+ }
+ this.entityIds = entityIds;
+ this.entityIndent = entityIndent;
+ this.entityChildrenMapped = {...this.entityChildrenMapped, [entityId]: true};
+ }
+ this.entityChildrenExpanded = {...this.entityChildrenExpanded, [entityId]: true};
+ },
+ onClickCollapse({entityId}) {
+ this.entityChildrenExpanded = {...this.entityChildrenExpanded, [entityId]: false};
+ },
+ async onClickDelete({entityId, name, description, type, ownerId}) {
+ this.processingDelete = {...this.processingDelete, [entityId]: true};
+
+ try {
+ await this.$store.dispatch("entity/deleteEntity", {
+ clientId: this.clientId,
+ entityId,
+ name,
+ description,
+ type,
+ ownerId
+ });
+ this.refreshData();
+ } catch (error) {
+ this.errors.push({
+ title: `Unknown error when deleting the entity '${entityId}'.`,
+ source: error, variant: "danger"
+ });
+ }
+
+ this.processingDelete = {...this.processingDelete, [entityId]: false};
+ },
refreshData() {
this.$store.dispatch("entity/fetchEntities", {clientId: this.clientId, ownerId: this.currentUsername});
}