mirror of
				https://github.com/Fabio286/antares.git
				synced 2025-06-05 21:59:22 +02:00 
			
		
		
		
	feat(ui): display table properties tab
This commit is contained in:
		| @@ -3,6 +3,11 @@ | ||||
|       <WorkspaceExploreBar :connection="connection" :is-selected="isSelected" /> | ||||
|       <div v-if="workspace.connected" class="workspace-tabs column columns col-gapless"> | ||||
|          <ul ref="tabWrap" class="tab tab-block column col-12"> | ||||
|             <li class="tab-item"> | ||||
|                <a class="tab-link workspace-tools-link"> | ||||
|                   <i class="mdi mdi-24px mdi-tools" /> | ||||
|                </a> | ||||
|             </li> | ||||
|             <li | ||||
|                v-if="workspace.breadcrumbs.table" | ||||
|                class="tab-item" | ||||
| @@ -56,11 +61,11 @@ | ||||
|                </a> | ||||
|             </li> | ||||
|          </ul> | ||||
|          <div v-show="selectedTab === 'prop'" class="column col-12"> | ||||
|             <p class="px-2"> | ||||
|                In future releases | ||||
|             </p> | ||||
|          </div> | ||||
|          <WorkspacePropsTab | ||||
|             v-show="selectedTab === 'prop'" | ||||
|             :connection="connection" | ||||
|             :table="workspace.breadcrumbs.table" | ||||
|          /> | ||||
|          <WorkspaceTableTab | ||||
|             v-show="selectedTab === 'data'" | ||||
|             :connection="connection" | ||||
| @@ -83,13 +88,15 @@ import Connection from '@/ipc-api/Connection'; | ||||
| import WorkspaceExploreBar from '@/components/WorkspaceExploreBar'; | ||||
| import WorkspaceQueryTab from '@/components/WorkspaceQueryTab'; | ||||
| import WorkspaceTableTab from '@/components/WorkspaceTableTab'; | ||||
| import WorkspacePropsTab from '@/components/WorkspacePropsTab'; | ||||
|  | ||||
| export default { | ||||
|    name: 'Workspace', | ||||
|    components: { | ||||
|       WorkspaceExploreBar, | ||||
|       WorkspaceQueryTab, | ||||
|       WorkspaceTableTab | ||||
|       WorkspaceTableTab, | ||||
|       WorkspacePropsTab | ||||
|    }, | ||||
|    props: { | ||||
|       connection: Object | ||||
| @@ -203,6 +210,11 @@ export default { | ||||
|         &.active a { | ||||
|           opacity: 1; | ||||
|         } | ||||
|  | ||||
|         .workspace-tools-link { | ||||
|           padding-bottom: 0; | ||||
|           padding-top: 0.3rem; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   | ||||
							
								
								
									
										202
									
								
								src/renderer/components/WorkspacePropsTab.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/renderer/components/WorkspacePropsTab.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| <template> | ||||
|    <div class="workspace-query-tab column col-12 columns col-gapless"> | ||||
|       <div class="workspace-query-runner column col-12"> | ||||
|          <div class="workspace-query-runner-footer"> | ||||
|             <div class="workspace-query-buttons"> | ||||
|                <button | ||||
|                   class="btn btn-primary btn-sm" | ||||
|                > | ||||
|                   <span>{{ $t('word.save') }}</span> | ||||
|                   <i class="mdi mdi-24px mdi-content-save ml-1" /> | ||||
|                </button> | ||||
|                <button | ||||
|                   class="btn btn-link btn-sm mr-0" | ||||
|                   :title="$t('message.clearChanges')" | ||||
|                > | ||||
|                   <span>{{ $t('word.clear') }}</span> | ||||
|                   <i class="mdi mdi-24px mdi-delete-sweep ml-1" /> | ||||
|                </button> | ||||
|  | ||||
|                <div class="divider-vert py-3" /> | ||||
|  | ||||
|                <button | ||||
|                   class="btn btn-dark btn-sm" | ||||
|                   :title="$t('message.addNewField')" | ||||
|                > | ||||
|                   <span>{{ $t('word.add') }}</span> | ||||
|                   <i class="mdi mdi-24px mdi-playlist-plus ml-1" /> | ||||
|                </button> | ||||
|                <button | ||||
|                   class="btn btn-dark btn-sm" | ||||
|                > | ||||
|                   <span>{{ $t('word.indexes') }}</span> | ||||
|                   <i class="mdi mdi-24px mdi-key mdi-rotate-45 ml-1" /> | ||||
|                </button> | ||||
|                <button | ||||
|                   class="btn btn-dark btn-sm" | ||||
|                > | ||||
|                   <span>{{ $t('word.foreignKeys') }}</span> | ||||
|                   <i class="mdi mdi-24px mdi-key-link ml-1" /> | ||||
|                </button> | ||||
|                <button | ||||
|                   class="btn btn-dark btn-sm" | ||||
|                > | ||||
|                   <span>{{ $t('word.options') }}</span> | ||||
|                   <i class="mdi mdi-24px mdi-cogs ml-1" /> | ||||
|                </button> | ||||
|             </div> | ||||
|          </div> | ||||
|       </div> | ||||
|       <div class="workspace-query-results column col-12"> | ||||
|          <WorkspacePropsTable | ||||
|             v-if="localFields" | ||||
|             ref="queryTable" | ||||
|             :results="localFields" | ||||
|             :tab-uid="tabUid" | ||||
|             :conn-uid="connection.uid" | ||||
|             :table="table" | ||||
|             :schema="schema" | ||||
|             mode="table" | ||||
|          /> | ||||
|       </div> | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Tables from '@/ipc-api/Tables'; | ||||
| import WorkspacePropsTable from '@/components/WorkspacePropsTable'; | ||||
| import { mapGetters, mapActions } from 'vuex'; | ||||
|  | ||||
| export default { | ||||
|    name: 'WorkspacePropsTab', | ||||
|    components: { | ||||
|       WorkspacePropsTable | ||||
|    }, | ||||
|    props: { | ||||
|       connection: Object, | ||||
|       table: String | ||||
|    }, | ||||
|    data () { | ||||
|       return { | ||||
|          tabUid: 'prop', | ||||
|          isQuering: false, | ||||
|          localFields: [], | ||||
|          localKeyUsage: [], | ||||
|          lastTable: null, | ||||
|          isAddModal: false | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       ...mapGetters({ | ||||
|          getWorkspace: 'workspaces/getWorkspace' | ||||
|       }), | ||||
|       workspace () { | ||||
|          return this.getWorkspace(this.connection.uid); | ||||
|       }, | ||||
|       isSelected () { | ||||
|          return this.workspace.selected_tab === 'prop'; | ||||
|       }, | ||||
|       schema () { | ||||
|          return this.workspace.breadcrumbs.schema; | ||||
|       } | ||||
|    }, | ||||
|    watch: { | ||||
|       table () { | ||||
|          if (this.isSelected) { | ||||
|             this.getFieldsData(); | ||||
|             this.lastTable = this.table; | ||||
|          } | ||||
|       }, | ||||
|       isSelected (val) { | ||||
|          if (val && this.lastTable !== this.table) { | ||||
|             this.getFieldsData(); | ||||
|             this.lastTable = this.table; | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
|    created () { | ||||
|       this.getFieldsData(); | ||||
|       window.addEventListener('keydown', this.onKey); | ||||
|    }, | ||||
|    methods: { | ||||
|       ...mapActions({ | ||||
|          addNotification: 'notifications/addNotification', | ||||
|          setTabFields: 'workspaces/setTabFields', | ||||
|          setTabKeyUsage: 'workspaces/setTabKeyUsage' | ||||
|       }), | ||||
|       async getFieldsData () { | ||||
|          if (!this.table) return; | ||||
|          this.isQuering = true; | ||||
|          const fieldsArr = []; | ||||
|          const keysArr = []; | ||||
|  | ||||
|          // if table changes clear cached values | ||||
|          if (this.lastTable !== this.table) | ||||
|             this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: [] }); | ||||
|  | ||||
|          const params = { | ||||
|             uid: this.connection.uid, | ||||
|             schema: this.schema, | ||||
|             table: this.workspace.breadcrumbs.table | ||||
|          }; | ||||
|  | ||||
|          try { // Columns data | ||||
|             const { status, response } = await Tables.getTableColumns(params); | ||||
|             if (status === 'success') { | ||||
|                this.localFields = response; | ||||
|                fieldsArr.push(response); | ||||
|             } | ||||
|             else | ||||
|                this.addNotification({ status: 'error', message: response }); | ||||
|          } | ||||
|          catch (err) { | ||||
|             this.addNotification({ status: 'error', message: err.stack }); | ||||
|          } | ||||
|  | ||||
|          try { // Key usage (foreign keys) | ||||
|             const { status, response } = await Tables.getKeyUsage(params); | ||||
|  | ||||
|             if (status === 'success') { | ||||
|                this.localKeyUsage = response; | ||||
|                keysArr.push(response); | ||||
|             } | ||||
|             else | ||||
|                this.addNotification({ status: 'error', message: response }); | ||||
|          } | ||||
|          catch (err) { | ||||
|             this.addNotification({ status: 'error', message: err.stack }); | ||||
|          } | ||||
|  | ||||
|          this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: fieldsArr }); | ||||
|          this.setTabKeyUsage({ cUid: this.connection.uid, tUid: this.tabUid, keyUsage: keysArr }); | ||||
|  | ||||
|          this.isQuering = false; | ||||
|       }, | ||||
|       reloadFields () { | ||||
|          this.getFieldsData(); | ||||
|       }, | ||||
|       showAddModal () { | ||||
|          this.isAddModal = true; | ||||
|       }, | ||||
|       hideAddModal () { | ||||
|          this.isAddModal = false; | ||||
|       }, | ||||
|       onKey (e) { | ||||
|          e.stopPropagation(); | ||||
|          if (e.key === 'F5') | ||||
|             this.reloadFields(); | ||||
|       }, | ||||
|       setRefreshInterval () { | ||||
|          if (this.refreshInterval) | ||||
|             clearInterval(this.refreshInterval); | ||||
|  | ||||
|          if (+this.autorefreshTimer) { | ||||
|             this.refreshInterval = setInterval(() => { | ||||
|                if (!this.isQuering) | ||||
|                   this.reloadFields(); | ||||
|             }, this.autorefreshTimer * 1000); | ||||
|          } | ||||
|       } | ||||
|    } | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										291
									
								
								src/renderer/components/WorkspacePropsTable.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								src/renderer/components/WorkspacePropsTable.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,291 @@ | ||||
| <template> | ||||
|    <div | ||||
|       ref="tableWrapper" | ||||
|       class="vscroll" | ||||
|       :style="{'height': resultsSize+'px'}" | ||||
|    > | ||||
|       <TableContext | ||||
|          v-if="isContext" | ||||
|          :context-event="contextEvent" | ||||
|          :selected-rows="selectedRows" | ||||
|          @delete-selected="deleteSelected" | ||||
|          @close-context="isContext = false" | ||||
|       /> | ||||
|       <div ref="propTable" class="table table-hover"> | ||||
|          <div class="thead"> | ||||
|             <div class="tr"> | ||||
|                <div class="th"> | ||||
|                   <div class="text-right"> | ||||
|                      {{ $t('word.order') }} | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="table-column-title"> | ||||
|                      {{ $tc('word.key', 2) }} | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="column-resizable"> | ||||
|                      <div class="table-column-title"> | ||||
|                         {{ $t('word.name') }} | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="column-resizable"> | ||||
|                      <div class="table-column-title"> | ||||
|                         {{ $t('word.type') }} | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="column-resizable"> | ||||
|                      <div class="table-column-title"> | ||||
|                         {{ $t('word.length') }} | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="column-resizable"> | ||||
|                      <div class="table-column-title"> | ||||
|                         {{ $t('word.unsigned') }} | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="column-resizable"> | ||||
|                      <div class="table-column-title"> | ||||
|                         {{ $t('message.allowNull') }} | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="column-resizable"> | ||||
|                      <div class="table-column-title"> | ||||
|                         {{ $t('message.zeroFill') }} | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="column-resizable"> | ||||
|                      <div class="table-column-title"> | ||||
|                         {{ $t('word.default') }} | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="column-resizable"> | ||||
|                      <div class="table-column-title"> | ||||
|                         {{ $t('word.comment') }} | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="th"> | ||||
|                   <div class="column-resizable"> | ||||
|                      <div class="table-column-title"> | ||||
|                         {{ $t('word.collation') }} | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|             </div> | ||||
|          </div> | ||||
|          <div ref="resultTable" class="tbody"> | ||||
|             <div | ||||
|                v-for="row in results" | ||||
|                :key="row.name" | ||||
|                class="tr" | ||||
|             > | ||||
|                <div class="td"> | ||||
|                   <div class="row-draggable"> | ||||
|                      <i class="mdi mdi-drag-horizontal row-draggable-icon" /> | ||||
|                      {{ row.order }} | ||||
|                   </div> | ||||
|                </div> | ||||
|                <div class="td"> | ||||
|                   <i | ||||
|                      v-if="row.key" | ||||
|                      :title="keyName(row.key)" | ||||
|                      class="mdi mdi-key column-key c-help pl-1" | ||||
|                      :class="`key-${row.key}`" | ||||
|                   /> | ||||
|                </div> | ||||
|                <div class="td"> | ||||
|                   {{ row.name }} | ||||
|                </div> | ||||
|                <div class="td text-uppercase" :class="`type-${row.type}`"> | ||||
|                   {{ row.type }} | ||||
|                </div> | ||||
|                <div class="td type-int"> | ||||
|                   {{ row.numLength || row.charLength || row.datePrecision }} | ||||
|                </div> | ||||
|                <div class="td"> | ||||
|                   <label class="form-checkbox"> | ||||
|                      <input type="checkbox" :checked="row.unsigned "> | ||||
|                      <i class="form-icon" /> | ||||
|                   </label> | ||||
|                </div> | ||||
|                <div class="td"> | ||||
|                   <label class="form-checkbox"> | ||||
|                      <input type="checkbox" :checked="row.nullable "> | ||||
|                      <i class="form-icon" /> | ||||
|                   </label> | ||||
|                </div> | ||||
|                <div class="td"> | ||||
|                   <label class="form-checkbox"> | ||||
|                      <input type="checkbox" :checked="row.zerofill "> | ||||
|                      <i class="form-icon" /> | ||||
|                   </label> | ||||
|                </div> | ||||
|                <div class="td"> | ||||
|                   {{ (row.autoIncrement ? 'AUTO_INCREMENT' : false) || row.default }} | ||||
|                </div> | ||||
|                <div class="td type-varchar"> | ||||
|                   {{ row.comment }} | ||||
|                </div> | ||||
|                <div class="td"> | ||||
|                   {{ row.collation }} | ||||
|                </div> | ||||
|             </div> | ||||
|          </div> | ||||
|       </div> | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import TableContext from '@/components/WorkspaceQueryTableContext'; | ||||
| import { mapActions, mapGetters } from 'vuex'; | ||||
|  | ||||
| export default { | ||||
|    name: 'WorkspacePropsTable', | ||||
|    components: { | ||||
|       TableContext | ||||
|    }, | ||||
|    props: { | ||||
|       results: Array, | ||||
|       tabUid: [String, Number], | ||||
|       connUid: String, | ||||
|       table: String, | ||||
|       schema: String, | ||||
|       mode: String | ||||
|    }, | ||||
|    data () { | ||||
|       return { | ||||
|          resultsSize: 1000, | ||||
|          localResults: [], | ||||
|          isContext: false, | ||||
|          contextEvent: null, | ||||
|          selectedCell: null, | ||||
|          selectedRows: [], | ||||
|          scrollElement: null | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       ...mapGetters({ | ||||
|          getWorkspaceTab: 'workspaces/getWorkspaceTab', | ||||
|          getWorkspace: 'workspaces/getWorkspace' | ||||
|       }), | ||||
|       workspaceSchema () { | ||||
|          return this.getWorkspace(this.connUid).breadcrumbs.schema; | ||||
|       }, | ||||
|       primaryField () { | ||||
|          return this.results.filter(field => ['pri', 'uni'].includes(field.key))[0] || false; | ||||
|       }, | ||||
|       tabProperties () { | ||||
|          return this.getWorkspaceTab(this.tabUid); | ||||
|       } | ||||
|  | ||||
|    }, | ||||
|    updated () { | ||||
|       if (this.$refs.propTable) | ||||
|          this.refreshScroller(); | ||||
|  | ||||
|       if (this.$refs.tableWrapper) | ||||
|          this.scrollElement = this.$refs.tableWrapper; | ||||
|    }, | ||||
|    mounted () { | ||||
|       window.addEventListener('resize', this.resizeResults); | ||||
|    }, | ||||
|    destroyed () { | ||||
|       window.removeEventListener('resize', this.resizeResults); | ||||
|    }, | ||||
|    methods: { | ||||
|       ...mapActions({ | ||||
|          addNotification: 'notifications/addNotification' | ||||
|       }), | ||||
|       keyName (key) { | ||||
|          switch (key) { | ||||
|             case 'pri': | ||||
|                return 'PRIMARY'; | ||||
|             case 'uni': | ||||
|                return 'UNIQUE'; | ||||
|             case 'mul': | ||||
|                return 'INDEX'; | ||||
|             default: | ||||
|                return 'UNKNOWN ' + key; | ||||
|          } | ||||
|       }, | ||||
|       resizeResults () { | ||||
|          if (this.$refs.resultTable) { | ||||
|             const el = this.$refs.tableWrapper; | ||||
|  | ||||
|             if (el) { | ||||
|                const footer = document.getElementById('footer'); | ||||
|                const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight; | ||||
|                this.resultsSize = size; | ||||
|             } | ||||
|             // this.$refs.resultTable.updateWindow(); | ||||
|          } | ||||
|       }, | ||||
|       refreshScroller () { | ||||
|          this.resizeResults(); | ||||
|       }, | ||||
|       contextMenu (event, cell) { | ||||
|          this.selectedCell = cell; | ||||
|          if (!this.selectedRows.includes(cell.id)) | ||||
|             this.selectedRows = [cell.id]; | ||||
|          this.contextEvent = event; | ||||
|          this.isContext = true; | ||||
|       } | ||||
|    } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .column-resizable { | ||||
|   &:hover, | ||||
|   &:active { | ||||
|     resize: horizontal; | ||||
|     overflow: hidden; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .row-draggable { | ||||
|   position: relative; | ||||
|   text-align: right; | ||||
|   padding-left: 28px; | ||||
|   cursor: grab; | ||||
|  | ||||
|   .row-draggable-icon { | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     font-size: 22px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .table-column-title { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| } | ||||
|  | ||||
| .form-checkbox { | ||||
|   padding: 0; | ||||
|   margin: 0; | ||||
|   line-height: 1; | ||||
|   min-height: auto; | ||||
|  | ||||
|   .form-icon { | ||||
|     top: 0.15rem; | ||||
|     left: calc(50% - 8px); | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @@ -221,7 +221,6 @@ export default { | ||||
|          return this.workspaceSchema; | ||||
|       }, | ||||
|       getPrimaryValue (row) { | ||||
|          console.log(row); | ||||
|          const primaryFieldName = Object.keys(row).find(prop => [ | ||||
|             this.primaryField.alias, | ||||
|             this.primaryField.name, | ||||
|   | ||||
| @@ -42,7 +42,17 @@ module.exports = { | ||||
|       connecting: 'Connecting', | ||||
|       name: 'Name', | ||||
|       collation: 'Collation', | ||||
|       autoRefresh: 'Auto-refresh' | ||||
|       clear: 'Clear', | ||||
|       options: 'Options', | ||||
|       autoRefresh: 'Auto-refresh', | ||||
|       indexes: 'Indexes', | ||||
|       foreignKeys: 'Foreign keys', | ||||
|       length: 'Length', | ||||
|       unsigned: 'Unsigned', | ||||
|       default: 'Default', | ||||
|       comment: 'Comment', | ||||
|       key: 'Key | Keys', | ||||
|       order: 'Order' | ||||
|    }, | ||||
|    message: { | ||||
|       appWelcome: 'Welcome to Antares SQL Client!', | ||||
| @@ -79,7 +89,13 @@ module.exports = { | ||||
|       databaseName: 'Database name', | ||||
|       serverDefault: 'Server default', | ||||
|       deleteDatabase: 'Delete database', | ||||
|       editDatabase: 'Edit database' | ||||
|       editDatabase: 'Edit database', | ||||
|       clearChanges: 'Clear changes', | ||||
|       addNewField: 'Add new field', | ||||
|       manageIndexes: 'Manage indexes', | ||||
|       manageForeignKeys: 'Manage foreign keys', | ||||
|       allowNull: 'Allow NULL', | ||||
|       zeroFill: 'Zero fill' | ||||
|    }, | ||||
|    // Date and Time | ||||
|    short: { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user