mirror of
				https://github.com/Fabio286/antares.git
				synced 2025-06-05 21:59:22 +02:00 
			
		
		
		
	refactor: ts and composition api on more components
This commit is contained in:
		
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "antares", | ||||
|   "version": "0.5.4", | ||||
|   "version": "0.5.5", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "antares", | ||||
|       "version": "0.5.4", | ||||
|       "version": "0.5.5", | ||||
|       "hasInstallScript": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|   | ||||
| @@ -1,10 +1,16 @@ | ||||
| import * as mysql from 'common/customizations/mysql'; | ||||
| import * as postgresql from 'common/customizations/postgresql'; | ||||
| import * as sqlite from 'common/customizations/sqlite'; | ||||
| import { Customizations } from 'common/interfaces/customizations'; | ||||
|  | ||||
| export default { | ||||
|    maria: mysql.customizations, | ||||
|    mysql: mysql.customizations, | ||||
|    pg: postgresql.customizations, | ||||
|    sqlite: sqlite.customizations | ||||
| } as { | ||||
|    maria: Customizations; | ||||
|    mysql: Customizations; | ||||
|    pg: Customizations; | ||||
|    sqlite: Customizations; | ||||
| }; | ||||
|   | ||||
| @@ -104,8 +104,6 @@ export default (connections: {[key: string]: antares.Client}) => { | ||||
|                   escapedParam = `"${sqlEscaper(params.content)}"`; | ||||
|                   break; | ||||
|                case 'pg': | ||||
|                   escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`; | ||||
|                   break; | ||||
|                case 'sqlite': | ||||
|                   escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`; | ||||
|                   break; | ||||
| @@ -248,8 +246,7 @@ export default (connections: {[key: string]: antares.Client}) => { | ||||
|  | ||||
|    ipcMain.handle('insert-table-rows', async (event, params) => { | ||||
|       try { // TODO: move to client classes | ||||
|          // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|          const insertObj: {[key: string]: any} = {}; | ||||
|          const insertObj: {[key: string]: string | number | boolean | Date | Buffer} = {}; | ||||
|          for (const key in params.row) { | ||||
|             const type = params.fields[key]; | ||||
|             let escapedParam; | ||||
| @@ -318,12 +315,10 @@ export default (connections: {[key: string]: antares.Client}) => { | ||||
|  | ||||
|    ipcMain.handle('insert-table-fake-rows', async (event, params: InsertRowsParams) => { | ||||
|       try { // TODO: move to client classes | ||||
|          // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|          const rows: {[key: string]: any}[] = []; | ||||
|          const rows: {[key: string]: string | number | boolean | Date | Buffer}[] = []; | ||||
|  | ||||
|          for (let i = 0; i < +params.repeat; i++) { | ||||
|             // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|             const insertObj: {[key: string]: any} = {}; | ||||
|             const insertObj: {[key: string]: string | number | boolean | Date | Buffer} = {}; | ||||
|  | ||||
|             for (const key in params.row) { | ||||
|                const type = params.fields[key]; | ||||
| @@ -341,6 +336,7 @@ export default (connections: {[key: string]: antares.Client}) => { | ||||
|                            escapedParam = `"${sqlEscaper(params.row[key].value)}"`; | ||||
|                            break; | ||||
|                         case 'pg': | ||||
|                         case 'sqlite': | ||||
|                            escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`; | ||||
|                            break; | ||||
|                      } | ||||
| @@ -381,8 +377,7 @@ export default (connections: {[key: string]: antares.Client}) => { | ||||
|                   insertObj[key] = escapedParam; | ||||
|                } | ||||
|                else { // Faker value | ||||
|                   // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|                   const parsedParams: {[key: string]: any} = {}; | ||||
|                   const parsedParams: {[key: string]: string | number | boolean | Date | Buffer} = {}; | ||||
|                   let fakeValue; | ||||
|  | ||||
|                   if (params.locale) | ||||
| @@ -402,7 +397,7 @@ export default (connections: {[key: string]: antares.Client}) => { | ||||
|  | ||||
|                   if (typeof fakeValue === 'string') { | ||||
|                      if (params.row[key].length) | ||||
|                         fakeValue = fakeValue.substr(0, params.row[key].length); | ||||
|                         fakeValue = fakeValue.substring(0, params.row[key].length); | ||||
|                      fakeValue = `'${sqlEscaper(fakeValue)}'`; | ||||
|                   } | ||||
|                   else if ([...DATE, ...DATETIME].includes(type)) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { ipcMain } from 'electron'; | ||||
| import { autoUpdater } from 'electron-updater'; | ||||
| import Store from 'electron-store'; | ||||
| const persistentStore = new Store({ name: 'settings' }); | ||||
| import * as Store from 'electron-store'; | ||||
| const persistentStore = new Store({ name: 'settings', clearInvalidConfig: true }); | ||||
| const isMacOS = process.platform === 'darwin'; | ||||
|  | ||||
| let mainWindow: Electron.IpcMainEvent; | ||||
|   | ||||
| @@ -37,6 +37,8 @@ | ||||
|             v-if="isOpen" | ||||
|             ref="optionList" | ||||
|             :class="`select__list-wrapper ${dropdownClass ? dropdownClass : '' }`" | ||||
|             @mousedown="isMouseDown = true" | ||||
|             @mouseup="handleMouseUpEvent()" | ||||
|          > | ||||
|             <ul class="select__list" @mousedown.prevent> | ||||
|                <li | ||||
| @@ -136,6 +138,7 @@ export default defineComponent({ | ||||
|    setup (props, { emit }) { | ||||
|       const hightlightedIndex = ref(0); | ||||
|       const isOpen = ref(false); | ||||
|       const isMouseDown = ref(false); | ||||
|       const internalValue = ref(props.modelValue || props.value); | ||||
|       const el = ref(null); | ||||
|       const searchInput = ref(null); | ||||
| @@ -151,7 +154,7 @@ export default defineComponent({ | ||||
|          if (typeof prop === 'function') | ||||
|             return prop(item); | ||||
|  | ||||
|          return item[prop] || item; | ||||
|          return item[prop] !== undefined ? item[prop] : item; | ||||
|       }; | ||||
|  | ||||
|       const flattenOptions = computed(() => { | ||||
| @@ -319,12 +322,24 @@ export default defineComponent({ | ||||
|       }; | ||||
|  | ||||
|       const handleBlurEvent = () => { | ||||
|          if (isMouseDown.value) return; | ||||
|          deactivate(); | ||||
|          emit('blur'); | ||||
|       }; | ||||
|  | ||||
|       const handleMouseUpEvent = () => { | ||||
|          isMouseDown.value = false; | ||||
|          searchInput.value.focus(); | ||||
|       }; | ||||
|  | ||||
|       const handleWheelEvent = (e) => { | ||||
|          if (!e.target.className.includes('select__')) deactivate(); | ||||
|       }; | ||||
|  | ||||
|       onMounted(() => { | ||||
|          window.addEventListener('resize', adjustListPosition); | ||||
|          window.addEventListener('wheel', handleWheelEvent); | ||||
|  | ||||
|          nextTick(() => { | ||||
|             // fix position when the component is created and opened at the same time | ||||
|             if (isOpen.value) { | ||||
| @@ -336,6 +351,7 @@ export default defineComponent({ | ||||
|       }); | ||||
|       onUnmounted(() => { | ||||
|          window.removeEventListener('resize', adjustListPosition); | ||||
|          window.removeEventListener('wheel', handleWheelEvent); | ||||
|       }); | ||||
|  | ||||
|       return { | ||||
| @@ -351,10 +367,12 @@ export default defineComponent({ | ||||
|          isSelected, | ||||
|          keyArrows, | ||||
|          isOpen, | ||||
|          isMouseDown, | ||||
|          hightlightedIndex, | ||||
|          optionList, | ||||
|          optionRefs, | ||||
|          handleBlurEvent | ||||
|          handleBlurEvent, | ||||
|          handleMouseUpEvent | ||||
|       }; | ||||
|    } | ||||
| }); | ||||
|   | ||||
| @@ -101,17 +101,10 @@ | ||||
| import { Component, computed, ComputedRef, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref, watch } from 'vue'; | ||||
| import * as moment from 'moment'; | ||||
| import { ConnectionParams } from 'common/interfaces/antares'; | ||||
| import { useHistoryStore } from '@/stores/history'; | ||||
| import { HistoryRecord, useHistoryStore } from '@/stores/history'; | ||||
| import { useConnectionsStore } from '@/stores/connections'; | ||||
| import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue'; | ||||
|  | ||||
| interface HistoryRow { | ||||
|    uid:string; | ||||
|    sql: string; | ||||
|    schema: string; | ||||
|    date: string; | ||||
| } | ||||
|  | ||||
| const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore(); | ||||
| const { getConnectionName } = useConnectionsStore(); | ||||
|  | ||||
| @@ -132,7 +125,7 @@ const searchTerm = ref(''); | ||||
| const localSearchTerm = ref(''); | ||||
|  | ||||
| const connectionName = computed(() => getConnectionName(props.connection.uid)); | ||||
| const history: ComputedRef<HistoryRow[]> = computed(() => (getHistoryByWorkspace(props.connection.uid) || [])); | ||||
| const history: ComputedRef<HistoryRecord[]> = computed(() => (getHistoryByWorkspace(props.connection.uid) || [])); | ||||
| const filteredHistory = computed(() => history.value.filter(q => q.sql.toLowerCase().search(searchTerm.value.toLowerCase()) >= 0)); | ||||
|  | ||||
| watch(searchTerm, () => { | ||||
| @@ -147,7 +140,7 @@ const copyQuery = (sql: string) => { | ||||
|    navigator.clipboard.writeText(sql); | ||||
| }; | ||||
|  | ||||
| const deleteQuery = (query: HistoryRow[]) => { | ||||
| const deleteQuery = (query: HistoryRecord[]) => { | ||||
|    deleteQueryFromHistory({ | ||||
|       workspace: props.connection.uid, | ||||
|       ...query | ||||
|   | ||||
| @@ -8,7 +8,8 @@ | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| <script setup lang="ts"> | ||||
| import { computed, onMounted, Prop, Ref, ref, toRef, watch } from 'vue'; | ||||
| import * as ace from 'ace-builds'; | ||||
| import 'ace-builds/webpack-resolver'; | ||||
| import '../libs/ext-language_tools'; | ||||
| @@ -16,329 +17,330 @@ import { storeToRefs } from 'pinia'; | ||||
| import { uidGen } from 'common/libs/uidGen'; | ||||
| import { useApplicationStore } from '@/stores/application'; | ||||
| import { useSettingsStore } from '@/stores/settings'; | ||||
| import { Workspace } from '@/stores/workspaces'; | ||||
| import Tables from '@/ipc-api/Tables'; | ||||
|  | ||||
| export default { | ||||
|    name: 'QueryEditor', | ||||
|    props: { | ||||
|       modelValue: String, | ||||
|       workspace: Object, | ||||
|       isSelected: Boolean, | ||||
|       schema: { type: String, default: '' }, | ||||
|       autoFocus: { type: Boolean, default: false }, | ||||
|       readOnly: { type: Boolean, default: false }, | ||||
|       height: { type: Number, default: 200 } | ||||
|    }, | ||||
|    emits: ['update:modelValue'], | ||||
|    setup () { | ||||
|       const editor = null; | ||||
|       const applicationStore = useApplicationStore(); | ||||
|       const settingsStore = useSettingsStore(); | ||||
| const editor: Ref<ace.Ace.Editor> = ref(null); | ||||
| const applicationStore = useApplicationStore(); | ||||
| const settingsStore = useSettingsStore(); | ||||
|  | ||||
|       const { setBaseCompleters } = applicationStore; | ||||
| const { setBaseCompleters } = applicationStore; | ||||
|  | ||||
|       const { baseCompleter } = storeToRefs(applicationStore); | ||||
|       const { | ||||
|          editorTheme, | ||||
|          editorFontSize, | ||||
|          autoComplete, | ||||
|          lineWrap | ||||
|       } = storeToRefs(settingsStore); | ||||
| const { baseCompleter } = storeToRefs(applicationStore); | ||||
| const { | ||||
|    editorTheme, | ||||
|    editorFontSize, | ||||
|    autoComplete, | ||||
|    lineWrap | ||||
| } = storeToRefs(settingsStore); | ||||
|  | ||||
|       return { | ||||
|          editor, | ||||
|          baseCompleter, | ||||
|          setBaseCompleters, | ||||
|          editorTheme, | ||||
|          editorFontSize, | ||||
|          autoComplete, | ||||
|          lineWrap | ||||
|       }; | ||||
|    }, | ||||
|    data () { | ||||
|       return { | ||||
|          cursorPosition: 0, | ||||
|          fields: [], | ||||
|          customCompleter: [], | ||||
|          id: uidGen(), | ||||
|          lastSchema: null | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       tables () { | ||||
|          return this.workspace | ||||
|             ? this.workspace.structure.filter(schema => schema.name === this.schema) | ||||
|                .reduce((acc, curr) => { | ||||
|                   acc.push(...curr.tables); | ||||
|                   return acc; | ||||
|                }, []).map(table => { | ||||
|                   return { | ||||
|                      name: table.name, | ||||
|                      type: table.type, | ||||
|                      fields: [] | ||||
|                   }; | ||||
|                }) | ||||
|             : []; | ||||
|       }, | ||||
|       triggers () { | ||||
|          return this.workspace | ||||
|             ? this.workspace.structure.filter(schema => schema.name === this.schema) | ||||
|                .reduce((acc, curr) => { | ||||
|                   acc.push(...curr.triggers); | ||||
|                   return acc; | ||||
|                }, []).map(trigger => { | ||||
|                   return { | ||||
|                      name: trigger.name, | ||||
|                      type: 'trigger' | ||||
|                   }; | ||||
|                }) | ||||
|             : []; | ||||
|       }, | ||||
|       procedures () { | ||||
|          return this.workspace | ||||
|             ? this.workspace.structure.filter(schema => schema.name === this.schema) | ||||
|                .reduce((acc, curr) => { | ||||
|                   acc.push(...curr.procedures); | ||||
|                   return acc; | ||||
|                }, []).map(procedure => { | ||||
|                   return { | ||||
|                      name: `${procedure.name}()`, | ||||
|                      type: 'routine' | ||||
|                   }; | ||||
|                }) | ||||
|             : []; | ||||
|       }, | ||||
|       functions () { | ||||
|          return this.workspace | ||||
|             ? this.workspace.structure.filter(schema => schema.name === this.schema) | ||||
|                .reduce((acc, curr) => { | ||||
|                   acc.push(...curr.functions); | ||||
|                   return acc; | ||||
|                }, []).map(func => { | ||||
|                   return { | ||||
|                      name: `${func.name}()`, | ||||
|                      type: 'function' | ||||
|                   }; | ||||
|                }) | ||||
|             : []; | ||||
|       }, | ||||
|       schedulers () { | ||||
|          return this.workspace | ||||
|             ? this.workspace.structure.filter(schema => schema.name === this.schema) | ||||
|                .reduce((acc, curr) => { | ||||
|                   acc.push(...curr.schedulers); | ||||
|                   return acc; | ||||
|                }, []).map(scheduler => { | ||||
|                   return { | ||||
|                      name: scheduler.name, | ||||
|                      type: 'scheduler' | ||||
|                   }; | ||||
|                }) | ||||
|             : []; | ||||
|       }, | ||||
|       mode () { | ||||
|          switch (this.workspace.client) { | ||||
|             case 'mysql': | ||||
|             case 'maria': | ||||
|                return 'mysql'; | ||||
|             case 'mssql': | ||||
|                return 'sqlserver'; | ||||
|             case 'pg': | ||||
|                return 'pgsql'; | ||||
|             default: | ||||
|                return 'sql'; | ||||
|          } | ||||
|       }, | ||||
|       lastWord () { | ||||
|          const charsBefore = this.modelValue.slice(0, this.cursorPosition); | ||||
|          const words = charsBefore.replaceAll('\n', ' ').split(' ').filter(Boolean); | ||||
|          return words.pop(); | ||||
|       }, | ||||
|       isLastWordATable () { | ||||
|          return /\w+\.\w*/gm.test(this.lastWord); | ||||
|       }, | ||||
|       fieldsCompleter () { | ||||
|          return { | ||||
|             getCompletions: (editor, session, pos, prefix, callback) => { | ||||
|                const completions = []; | ||||
|                this.fields.forEach(field => { | ||||
|                   completions.push({ | ||||
|                      value: field, | ||||
|                      meta: 'column', | ||||
|                      score: 1000 | ||||
|                   }); | ||||
|                }); | ||||
|                callback(null, completions); | ||||
|             } | ||||
|          }; | ||||
| const props = defineProps({ | ||||
|    modelValue: String, | ||||
|    workspace: Object as Prop<Workspace>, | ||||
|    isSelected: Boolean, | ||||
|    schema: { type: String, default: '' }, | ||||
|    autoFocus: { type: Boolean, default: false }, | ||||
|    readOnly: { type: Boolean, default: false }, | ||||
|    height: { type: Number, default: 200 } | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits(['update:modelValue']); | ||||
|  | ||||
| const cursorPosition = ref(0); | ||||
| const fields = ref([]); | ||||
| const customCompleter = ref([]); | ||||
| const id = ref(uidGen()); | ||||
| const lastSchema: Ref<string> = ref(null); | ||||
|  | ||||
| const tables = computed(() => { | ||||
|    return props.workspace | ||||
|       ? props.workspace.structure.filter(schema => schema.name === props.schema) | ||||
|          .reduce((acc, curr) => { | ||||
|             acc.push(...curr.tables); | ||||
|             return acc; | ||||
|          }, []).map(table => { | ||||
|             return { | ||||
|                name: table.name as string, | ||||
|                type: table.type as string, | ||||
|                fields: [] | ||||
|             }; | ||||
|          }) | ||||
|       : []; | ||||
| }); | ||||
|  | ||||
| const triggers = computed(() => { | ||||
|    return props.workspace | ||||
|       ? props.workspace.structure.filter(schema => schema.name === props.schema) | ||||
|          .reduce((acc, curr) => { | ||||
|             acc.push(...curr.triggers); | ||||
|             return acc; | ||||
|          }, []).map(trigger => { | ||||
|             return { | ||||
|                name: trigger.name as string, | ||||
|                type: 'trigger' | ||||
|             }; | ||||
|          }) | ||||
|       : []; | ||||
| }); | ||||
|  | ||||
| const procedures = computed(() => { | ||||
|    return props.workspace | ||||
|       ? props.workspace.structure.filter(schema => schema.name === props.schema) | ||||
|          .reduce((acc, curr) => { | ||||
|             acc.push(...curr.procedures); | ||||
|             return acc; | ||||
|          }, []).map(procedure => { | ||||
|             return { | ||||
|                name: `${procedure.name}()`, | ||||
|                type: 'routine' | ||||
|             }; | ||||
|          }) | ||||
|       : []; | ||||
| }); | ||||
|  | ||||
| const functions = computed(() => { | ||||
|    return props.workspace | ||||
|       ? props.workspace.structure.filter(schema => schema.name === props.schema) | ||||
|          .reduce((acc, curr) => { | ||||
|             acc.push(...curr.functions); | ||||
|             return acc; | ||||
|          }, []).map(func => { | ||||
|             return { | ||||
|                name: `${func.name}()`, | ||||
|                type: 'function' | ||||
|             }; | ||||
|          }) | ||||
|       : []; | ||||
| }); | ||||
|  | ||||
| const schedulers = computed(() => { | ||||
|    return props.workspace | ||||
|       ? props.workspace.structure.filter(schema => schema.name === props.schema) | ||||
|          .reduce((acc, curr) => { | ||||
|             acc.push(...curr.schedulers); | ||||
|             return acc; | ||||
|          }, []).map(scheduler => { | ||||
|             return { | ||||
|                name: scheduler.name as string, | ||||
|                type: 'scheduler' | ||||
|             }; | ||||
|          }) | ||||
|       : []; | ||||
| }); | ||||
|  | ||||
| const mode = computed(() => { | ||||
|    switch (props.workspace.client) { | ||||
|       case 'mysql': | ||||
|       case 'maria': | ||||
|          return 'mysql'; | ||||
|       case 'mssql': | ||||
|          return 'sqlserver'; | ||||
|       case 'pg': | ||||
|          return 'pgsql'; | ||||
|       default: | ||||
|          return 'sql'; | ||||
|    } | ||||
| }); | ||||
|  | ||||
| const lastWord = computed(() => { | ||||
|    const charsBefore = props.modelValue.slice(0, cursorPosition.value); | ||||
|    const words = charsBefore.replaceAll('\n', ' ').split(' ').filter(Boolean); | ||||
|    return words.pop(); | ||||
| }); | ||||
|  | ||||
| const isLastWordATable = computed(() => /\w+\.\w*/gm.test(lastWord.value)); | ||||
|  | ||||
| const fieldsCompleter = computed(() => { | ||||
|    return { | ||||
|       getCompletions: (editor: never, session: never, pos: never, prefix: never, callback: (err: null, response: ace.Ace.Completion[]) => void) => { | ||||
|          const completions: ace.Ace.Completion[] = []; | ||||
|          fields.value.forEach(field => { | ||||
|             completions.push({ | ||||
|                value: field, | ||||
|                meta: 'column', | ||||
|                score: 1000 | ||||
|             }); | ||||
|          }); | ||||
|          callback(null, completions); | ||||
|       } | ||||
|    }, | ||||
|    watch: { | ||||
|       modelValue () { | ||||
|          this.cursorPosition = this.editor.session.doc.positionToIndex(this.editor.getCursorPosition()); | ||||
|       }, | ||||
|       editorTheme () { | ||||
|          if (this.editor) | ||||
|             this.editor.setTheme(`ace/theme/${this.editorTheme}`); | ||||
|       }, | ||||
|       editorFontSize () { | ||||
|          const sizes = { | ||||
|             small: '12px', | ||||
|             medium: '14px', | ||||
|             large: '16px' | ||||
|          }; | ||||
|    }; | ||||
| }); | ||||
|  | ||||
|          if (this.editor) { | ||||
|             this.editor.setOptions({ | ||||
|                fontSize: sizes[this.editorFontSize] | ||||
| const setCustomCompleter = () => { | ||||
|    editor.value.completers.push({ | ||||
|       getCompletions: (editor, session, pos, prefix, callback: (err: null, response: ace.Ace.Completion[]) => void) => { | ||||
|          const completions: ace.Ace.Completion[] = []; | ||||
|          [ | ||||
|             ...tables.value, | ||||
|             ...triggers.value, | ||||
|             ...procedures.value, | ||||
|             ...functions.value, | ||||
|             ...schedulers.value | ||||
|          ].forEach(el => { | ||||
|             completions.push({ | ||||
|                value: el.name, | ||||
|                meta: el.type, | ||||
|                score: 1000 | ||||
|             }); | ||||
|          } | ||||
|       }, | ||||
|       autoComplete () { | ||||
|          if (this.editor) { | ||||
|             this.editor.setOptions({ | ||||
|                enableLiveAutocompletion: this.autoComplete | ||||
|             }); | ||||
|          } | ||||
|       }, | ||||
|       lineWrap () { | ||||
|          if (this.editor) { | ||||
|             this.editor.setOptions({ | ||||
|                wrap: this.lineWrap | ||||
|             }); | ||||
|          } | ||||
|       }, | ||||
|       isSelected () { | ||||
|          if (this.isSelected) { | ||||
|             this.lastSchema = this.schema; | ||||
|             this.editor.resize(); | ||||
|          } | ||||
|       }, | ||||
|       height () { | ||||
|          setTimeout(() => { | ||||
|             this.editor.resize(); | ||||
|          }, 20); | ||||
|       }, | ||||
|       lastSchema () { | ||||
|          if (this.editor) { | ||||
|             this.editor.completers = this.baseCompleter.map(el => Object.assign({}, el)); | ||||
|             this.setCustomCompleter(); | ||||
|          } | ||||
|          }); | ||||
|          callback(null, completions); | ||||
|       } | ||||
|    }, | ||||
|    created () { | ||||
|       this.lastSchema = this.schema; | ||||
|    }, | ||||
|    mounted () { | ||||
|       this.editor = ace.edit(`editor-${this.id}`, { | ||||
|          mode: `ace/mode/${this.mode}`, | ||||
|          theme: `ace/theme/${this.editorTheme}`, | ||||
|          value: this.modelValue, | ||||
|          fontSize: '14px', | ||||
|          printMargin: false, | ||||
|          readOnly: this.readOnly | ||||
|    }); | ||||
|  | ||||
|    customCompleter.value = editor.value.completers; | ||||
| }; | ||||
|  | ||||
| watch(() => props.modelValue, () => { | ||||
|    // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|    cursorPosition.value = (editor.value.session as any).doc.positionToIndex(editor.value.getCursorPosition()); | ||||
| }); | ||||
|  | ||||
| watch(editorTheme, () => { | ||||
|    if (editor.value) | ||||
|       editor.value.setTheme(`ace/theme/${editorTheme.value}`); | ||||
| }); | ||||
|  | ||||
| watch(editorFontSize, () => { | ||||
|    const sizes = { | ||||
|       small: '12px', | ||||
|       medium: '14px', | ||||
|       large: '16px' | ||||
|    }; | ||||
|  | ||||
|    if (editor.value) { | ||||
|       editor.value.setOptions({ | ||||
|          fontSize: sizes[editorFontSize.value] | ||||
|       }); | ||||
|    } | ||||
| }); | ||||
|  | ||||
|       this.editor.setOptions({ | ||||
|          enableBasicAutocompletion: true, | ||||
|          wrap: this.lineWrap, | ||||
|          enableSnippets: true, | ||||
|          enableLiveAutocompletion: this.autoComplete | ||||
| watch(autoComplete, () => { | ||||
|    if (editor.value) { | ||||
|       editor.value.setOptions({ | ||||
|          enableLiveAutocompletion: autoComplete.value | ||||
|       }); | ||||
|    } | ||||
| }); | ||||
|  | ||||
|       if (!this.baseCompleter.length) | ||||
|          this.setBaseCompleters(this.editor.completers.map(el => Object.assign({}, el))); | ||||
| watch(lineWrap, () => { | ||||
|    if (editor.value) { | ||||
|       editor.value.setOptions({ | ||||
|          wrap: lineWrap.value | ||||
|       }); | ||||
|    } | ||||
| }); | ||||
|  | ||||
|       this.setCustomCompleter(); | ||||
| watch(() => props.isSelected, () => { | ||||
|    if (props.isSelected) { | ||||
|       lastSchema.value = props.schema; | ||||
|       editor.value.resize(); | ||||
|    } | ||||
| }); | ||||
|  | ||||
|       this.editor.commands.on('afterExec', e => { | ||||
|          if (['insertstring', 'backspace', 'del'].includes(e.command.name)) { | ||||
|             if (this.isLastWordATable || e.args === '.') { | ||||
|                if (e.args !== ' ') { | ||||
|                   const table = this.tables.find(t => t.name === this.lastWord.split('.').pop().trim()); | ||||
| watch(() => props.height, () => { | ||||
|    setTimeout(() => { | ||||
|       editor.value.resize(); | ||||
|    }, 20); | ||||
| }); | ||||
|  | ||||
|                   if (table) { | ||||
|                      const params = { | ||||
|                         uid: this.workspace.uid, | ||||
|                         schema: this.schema, | ||||
|                         table: table.name | ||||
|                      }; | ||||
| watch(lastSchema, () => { | ||||
|    if (editor.value) { | ||||
|       editor.value.completers = baseCompleter.value.map(el => Object.assign({}, el)); | ||||
|       setCustomCompleter(); | ||||
|    } | ||||
| }); | ||||
|  | ||||
|                      Tables.getTableColumns(params).then(res => { | ||||
|                         if (res.response.length) | ||||
|                            this.fields = res.response.map(field => field.name); | ||||
|                         this.editor.completers = [this.fieldsCompleter]; | ||||
|                         this.editor.execCommand('startAutocomplete'); | ||||
|                      }).catch(console.log); | ||||
|                   } | ||||
|                   else | ||||
|                      this.editor.completers = this.customCompleter; | ||||
| lastSchema.value = toRef(props, 'schema').value; | ||||
|  | ||||
| onMounted(() => { | ||||
|    editor.value = ace.edit(`editor-${id.value}`, { | ||||
|       mode: `ace/mode/${mode.value}`, | ||||
|       theme: `ace/theme/${editorTheme.value}`, | ||||
|       value: props.modelValue, | ||||
|       fontSize: 14, | ||||
|       printMargin: false, | ||||
|       readOnly: props.readOnly | ||||
|    }); | ||||
|  | ||||
|    editor.value.setOptions({ | ||||
|       enableBasicAutocompletion: true, | ||||
|       wrap: lineWrap.value, | ||||
|       enableSnippets: true, | ||||
|       enableLiveAutocompletion: autoComplete.value | ||||
|    }); | ||||
|  | ||||
|    if (!baseCompleter.value.length) | ||||
|       setBaseCompleters(editor.value.completers.map(el => Object.assign({}, el))); | ||||
|  | ||||
|    setCustomCompleter(); | ||||
|  | ||||
|    editor.value.commands.on('afterExec', (e: { args: string; command: { name: string } }) => { | ||||
|       if (['insertstring', 'backspace', 'del'].includes(e.command.name)) { | ||||
|          if (isLastWordATable.value || e.args === '.') { | ||||
|             if (e.args !== ' ') { | ||||
|                const table = tables.value.find(t => t.name === lastWord.value.split('.').pop().trim()); | ||||
|  | ||||
|                if (table) { | ||||
|                   const params = { | ||||
|                      uid: props.workspace.uid, | ||||
|                      schema: props.schema, | ||||
|                      table: table.name | ||||
|                   }; | ||||
|  | ||||
|                   Tables.getTableColumns(params).then(res => { | ||||
|                      if (res.response.length) | ||||
|                         fields.value = res.response.map((field: { name: string }) => field.name); | ||||
|                      editor.value.completers = [fieldsCompleter.value]; | ||||
|                      editor.value.execCommand('startAutocomplete'); | ||||
|                   }).catch(console.log); | ||||
|                } | ||||
|                else | ||||
|                   this.editor.completers = this.customCompleter; | ||||
|                   editor.value.completers = customCompleter.value; | ||||
|             } | ||||
|             else | ||||
|                this.editor.completers = this.customCompleter; | ||||
|                editor.value.completers = customCompleter.value; | ||||
|          } | ||||
|       }); | ||||
|  | ||||
|       this.editor.session.on('change', () => { | ||||
|          const content = this.editor.getValue(); | ||||
|          this.$emit('update:modelValue', content); | ||||
|       }); | ||||
|  | ||||
|       this.editor.on('guttermousedown', e => { | ||||
|          const target = e.domEvent.target; | ||||
|          if (target.className.indexOf('ace_gutter-cell') === -1) | ||||
|             return; | ||||
|          if (e.clientX > 25 + target.getBoundingClientRect().left) | ||||
|             return; | ||||
|  | ||||
|          const row = e.getDocumentPosition().row; | ||||
|          const breakpoints = e.editor.session.getBreakpoints(row, 0); | ||||
|          if (typeof breakpoints[row] === typeof undefined) | ||||
|             e.editor.session.setBreakpoint(row); | ||||
|          else | ||||
|             e.editor.session.clearBreakpoint(row); | ||||
|          e.stop(); | ||||
|       }); | ||||
|  | ||||
|       if (this.autoFocus) { | ||||
|          setTimeout(() => { | ||||
|             this.editor.focus(); | ||||
|             this.editor.resize(); | ||||
|          }, 20); | ||||
|             editor.value.completers = customCompleter.value; | ||||
|       } | ||||
|    }); | ||||
|  | ||||
|    // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|    (editor.value.session as any).on('change', () => { | ||||
|       const content = editor.value.getValue(); | ||||
|       emit('update:modelValue', content); | ||||
|    }); | ||||
|  | ||||
|    // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|    (editor.value as any).on('guttermousedown', (e: any) => { | ||||
|       const target = e.domEvent.target; | ||||
|       if (target.className.indexOf('ace_gutter-cell') === -1) | ||||
|          return; | ||||
|       if (e.clientX > 25 + target.getBoundingClientRect().left) | ||||
|          return; | ||||
|  | ||||
|       const row = e.getDocumentPosition().row; | ||||
|       const breakpoints = e.editor.value.session.getBreakpoints(row, 0); | ||||
|       if (typeof breakpoints[row] === typeof undefined) | ||||
|          e.editor.value.session.setBreakpoint(row); | ||||
|       else | ||||
|          e.editor.value.session.clearBreakpoint(row); | ||||
|       e.stop(); | ||||
|    }); | ||||
|  | ||||
|    if (props.autoFocus) { | ||||
|       setTimeout(() => { | ||||
|          this.editor.resize(); | ||||
|          editor.value.focus(); | ||||
|          editor.value.resize(); | ||||
|       }, 20); | ||||
|    }, | ||||
|    methods: { | ||||
|       setCustomCompleter () { | ||||
|          this.editor.completers.push({ | ||||
|             getCompletions: (editor, session, pos, prefix, callback) => { | ||||
|                const completions = []; | ||||
|                [ | ||||
|                   ...this.tables, | ||||
|                   ...this.triggers, | ||||
|                   ...this.procedures, | ||||
|                   ...this.functions, | ||||
|                   ...this.schedulers | ||||
|                ].forEach(el => { | ||||
|                   completions.push({ | ||||
|                      value: el.name, | ||||
|                      meta: el.type | ||||
|                   }); | ||||
|                }); | ||||
|                callback(null, completions); | ||||
|             } | ||||
|          }); | ||||
|  | ||||
|          this.customCompleter = this.editor.completers; | ||||
|       } | ||||
|    } | ||||
| }; | ||||
|  | ||||
|    setTimeout(() => { | ||||
|       editor.value.resize(); | ||||
|    }, 20); | ||||
| }); | ||||
|  | ||||
| defineExpose({ editor }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
|   | ||||
| @@ -29,83 +29,66 @@ | ||||
|    </BaseContextMenu> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| <script setup lang="ts"> | ||||
| import { computed, Prop, ref } from 'vue'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
| import { uidGen } from 'common/libs/uidGen'; | ||||
| import { useConnectionsStore } from '@/stores/connections'; | ||||
| import { useWorkspacesStore } from '@/stores/workspaces'; | ||||
| import BaseContextMenu from '@/components/BaseContextMenu'; | ||||
| import ConfirmModal from '@/components/BaseConfirmModal'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
| import BaseContextMenu from '@/components/BaseContextMenu.vue'; | ||||
| import ConfirmModal from '@/components/BaseConfirmModal.vue'; | ||||
| import { ConnectionParams } from 'common/interfaces/antares'; | ||||
|  | ||||
| export default { | ||||
|    name: 'SettingBarContext', | ||||
|    components: { | ||||
|       BaseContextMenu, | ||||
|       ConfirmModal | ||||
|    }, | ||||
|    props: { | ||||
|       contextEvent: MouseEvent, | ||||
|       contextConnection: Object | ||||
|    }, | ||||
|    emits: ['close-context'], | ||||
|    setup () { | ||||
|       const { | ||||
|          getConnectionName, | ||||
|          addConnection, | ||||
|          deleteConnection | ||||
|       } = useConnectionsStore(); | ||||
|       const workspacesStore = useWorkspacesStore(); | ||||
|       const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); | ||||
| const { | ||||
|    getConnectionName, | ||||
|    addConnection, | ||||
|    deleteConnection | ||||
| } = useConnectionsStore(); | ||||
| const workspacesStore = useWorkspacesStore(); | ||||
| const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); | ||||
|  | ||||
|       const { selectWorkspace } = workspacesStore; | ||||
| const { selectWorkspace } = workspacesStore; | ||||
|  | ||||
|       return { | ||||
|          getConnectionName, | ||||
|          addConnection, | ||||
|          deleteConnection, | ||||
|          selectedWorkspace, | ||||
|          selectWorkspace | ||||
|       }; | ||||
|    }, | ||||
|    data () { | ||||
|       return { | ||||
|          isConfirmModal: false, | ||||
|          isEditModal: false | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       connectionName () { | ||||
|          return this.getConnectionName(this.contextConnection.uid); | ||||
|       } | ||||
|    }, | ||||
|    methods: { | ||||
|       confirmDeleteConnection () { | ||||
|          if (this.selectedWorkspace === this.contextConnection.uid) | ||||
|             this.selectWorkspace(); | ||||
|          this.deleteConnection(this.contextConnection); | ||||
|          this.closeContext(); | ||||
|       }, | ||||
|       duplicateConnection () { | ||||
|          let connectionCopy = Object.assign({}, this.contextConnection); | ||||
|          connectionCopy = { | ||||
|             ...connectionCopy, | ||||
|             uid: uidGen('C'), | ||||
|             name: connectionCopy.name ? `${connectionCopy?.name}_copy` : '' | ||||
|          }; | ||||
| const props = defineProps({ | ||||
|    contextEvent: MouseEvent, | ||||
|    contextConnection: Object as Prop<ConnectionParams> | ||||
| }); | ||||
|  | ||||
|          this.addConnection(connectionCopy); | ||||
|          this.closeContext(); | ||||
|       }, | ||||
|       showConfirmModal () { | ||||
|          this.isConfirmModal = true; | ||||
|       }, | ||||
|       hideConfirmModal () { | ||||
|          this.isConfirmModal = false; | ||||
|          this.closeContext(); | ||||
|       }, | ||||
|       closeContext () { | ||||
|          this.$emit('close-context'); | ||||
|       } | ||||
|    } | ||||
| const emit = defineEmits(['close-context']); | ||||
|  | ||||
| const isConfirmModal = ref(false); | ||||
|  | ||||
| const connectionName = computed(() => getConnectionName(props.contextConnection.uid)); | ||||
|  | ||||
| const confirmDeleteConnection = () => { | ||||
|    if (selectedWorkspace.value === props.contextConnection.uid) | ||||
|       selectWorkspace(null); | ||||
|    deleteConnection(props.contextConnection); | ||||
|    closeContext(); | ||||
| }; | ||||
|  | ||||
| const duplicateConnection = () => { | ||||
|    let connectionCopy = Object.assign({}, props.contextConnection); | ||||
|    connectionCopy = { | ||||
|       ...connectionCopy, | ||||
|       uid: uidGen('C'), | ||||
|       name: connectionCopy.name ? `${connectionCopy?.name}_copy` : '' | ||||
|    }; | ||||
|  | ||||
|    addConnection(connectionCopy); | ||||
|    closeContext(); | ||||
| }; | ||||
|  | ||||
| const showConfirmModal = () => { | ||||
|    isConfirmModal.value = true; | ||||
| }; | ||||
|  | ||||
| const hideConfirmModal = () => { | ||||
|    isConfirmModal.value = false; | ||||
|    closeContext(); | ||||
| }; | ||||
|  | ||||
| const closeContext = () => { | ||||
|    emit('close-context'); | ||||
| }; | ||||
| </script> | ||||
|   | ||||
| @@ -29,7 +29,7 @@ const settingsStore = useSettingsStore(); | ||||
| const { removeNotification } = notificationsStore; | ||||
|  | ||||
| const { notifications } = storeToRefs(notificationsStore); | ||||
| const { notificationsTimeout } = storeToRefs(settingsStore) as {notificationsTimeout: Ref<number>};// TODO: temp | ||||
| const { notificationsTimeout } = storeToRefs(settingsStore); | ||||
|  | ||||
| const timeouts: Ref<{[key: string]: NodeJS.Timeout}> = ref({}); | ||||
|  | ||||
|   | ||||
| @@ -43,7 +43,7 @@ const { notes } = storeToRefs(scratchpadStore); | ||||
| const { changeNotes } = scratchpadStore; | ||||
| const { hideScratchpad } = applicationStore; | ||||
|  | ||||
| const localNotes: Ref<string> = ref(notes.value as string);// TODO: temp | ||||
| const localNotes = ref(notes.value); | ||||
| const debounceTimeout: Ref<NodeJS.Timeout> = ref(null); | ||||
|  | ||||
| watch(localNotes, () => { | ||||
|   | ||||
| @@ -86,7 +86,7 @@ const connections = computed({ | ||||
|    get () { | ||||
|       return getConnections.value; | ||||
|    }, | ||||
|    set (value) { | ||||
|    set (value: ConnectionParams[]) { | ||||
|       updateConnections(value); | ||||
|    } | ||||
| }); | ||||
|   | ||||
| @@ -484,246 +484,196 @@ | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| <script setup lang="ts"> | ||||
| import { computed, onBeforeUnmount, Prop, ref, watch } from 'vue'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
| import Draggable from 'vuedraggable'; | ||||
| import * as Draggable from 'vuedraggable'; | ||||
| import Connection from '@/ipc-api/Connection'; | ||||
| import { useWorkspacesStore } from '@/stores/workspaces'; | ||||
| import { useWorkspacesStore, WorkspaceTab } from '@/stores/workspaces'; | ||||
| import { ConnectionParams } from 'common/interfaces/antares'; | ||||
|  | ||||
| import WorkspaceEmptyState from '@/components/WorkspaceEmptyState'; | ||||
| import WorkspaceExploreBar from '@/components/WorkspaceExploreBar'; | ||||
| import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel'; | ||||
| import WorkspaceTabQuery from '@/components/WorkspaceTabQuery'; | ||||
| import WorkspaceTabTable from '@/components/WorkspaceTabTable'; | ||||
| import WorkspaceEmptyState from '@/components/WorkspaceEmptyState.vue'; | ||||
| import WorkspaceExploreBar from '@/components/WorkspaceExploreBar.vue'; | ||||
| import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel.vue'; | ||||
| import WorkspaceTabQuery from '@/components/WorkspaceTabQuery.vue'; | ||||
| import WorkspaceTabTable from '@/components/WorkspaceTabTable.vue'; | ||||
|  | ||||
| import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable'; | ||||
| import WorkspaceTabNewView from '@/components/WorkspaceTabNewView'; | ||||
| import WorkspaceTabNewTrigger from '@/components/WorkspaceTabNewTrigger'; | ||||
| import WorkspaceTabNewRoutine from '@/components/WorkspaceTabNewRoutine'; | ||||
| import WorkspaceTabNewFunction from '@/components/WorkspaceTabNewFunction'; | ||||
| import WorkspaceTabNewScheduler from '@/components/WorkspaceTabNewScheduler'; | ||||
| import WorkspaceTabNewTriggerFunction from '@/components/WorkspaceTabNewTriggerFunction'; | ||||
| import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable.vue'; | ||||
| import WorkspaceTabNewView from '@/components/WorkspaceTabNewView.vue'; | ||||
| import WorkspaceTabNewTrigger from '@/components/WorkspaceTabNewTrigger.vue'; | ||||
| import WorkspaceTabNewRoutine from '@/components/WorkspaceTabNewRoutine.vue'; | ||||
| import WorkspaceTabNewFunction from '@/components/WorkspaceTabNewFunction.vue'; | ||||
| import WorkspaceTabNewScheduler from '@/components/WorkspaceTabNewScheduler.vue'; | ||||
| import WorkspaceTabNewTriggerFunction from '@/components/WorkspaceTabNewTriggerFunction.vue'; | ||||
|  | ||||
| import WorkspaceTabPropsTable from '@/components/WorkspaceTabPropsTable'; | ||||
| import WorkspaceTabPropsView from '@/components/WorkspaceTabPropsView'; | ||||
| import WorkspaceTabPropsTrigger from '@/components/WorkspaceTabPropsTrigger'; | ||||
| import WorkspaceTabPropsTriggerFunction from '@/components/WorkspaceTabPropsTriggerFunction'; | ||||
| import WorkspaceTabPropsRoutine from '@/components/WorkspaceTabPropsRoutine'; | ||||
| import WorkspaceTabPropsFunction from '@/components/WorkspaceTabPropsFunction'; | ||||
| import WorkspaceTabPropsScheduler from '@/components/WorkspaceTabPropsScheduler'; | ||||
| import ModalProcessesList from '@/components/ModalProcessesList'; | ||||
| import ModalDiscardChanges from '@/components/ModalDiscardChanges'; | ||||
| import WorkspaceTabPropsTable from '@/components/WorkspaceTabPropsTable.vue'; | ||||
| import WorkspaceTabPropsView from '@/components/WorkspaceTabPropsView.vue'; | ||||
| import WorkspaceTabPropsTrigger from '@/components/WorkspaceTabPropsTrigger.vue'; | ||||
| import WorkspaceTabPropsTriggerFunction from '@/components/WorkspaceTabPropsTriggerFunction.vue'; | ||||
| import WorkspaceTabPropsRoutine from '@/components/WorkspaceTabPropsRoutine.vue'; | ||||
| import WorkspaceTabPropsFunction from '@/components/WorkspaceTabPropsFunction.vue'; | ||||
| import WorkspaceTabPropsScheduler from '@/components/WorkspaceTabPropsScheduler.vue'; | ||||
| import ModalProcessesList from '@/components/ModalProcessesList.vue'; | ||||
| import ModalDiscardChanges from '@/components/ModalDiscardChanges.vue'; | ||||
|  | ||||
| export default { | ||||
|    name: 'Workspace', | ||||
|    components: { | ||||
|       Draggable, | ||||
|       WorkspaceEmptyState, | ||||
|       WorkspaceExploreBar, | ||||
|       WorkspaceEditConnectionPanel, | ||||
|       WorkspaceTabQuery, | ||||
|       WorkspaceTabTable, | ||||
|       WorkspaceTabNewTable, | ||||
|       WorkspaceTabPropsTable, | ||||
|       WorkspaceTabNewView, | ||||
|       WorkspaceTabPropsView, | ||||
|       WorkspaceTabNewTrigger, | ||||
|       WorkspaceTabPropsTrigger, | ||||
|       WorkspaceTabNewTriggerFunction, | ||||
|       WorkspaceTabPropsTriggerFunction, | ||||
|       WorkspaceTabNewRoutine, | ||||
|       WorkspaceTabNewFunction, | ||||
|       WorkspaceTabPropsRoutine, | ||||
|       WorkspaceTabPropsFunction, | ||||
|       WorkspaceTabNewScheduler, | ||||
|       WorkspaceTabPropsScheduler, | ||||
|       ModalProcessesList, | ||||
|       ModalDiscardChanges | ||||
| const workspacesStore = useWorkspacesStore(); | ||||
|  | ||||
| const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); | ||||
|  | ||||
| const { | ||||
|    getWorkspace, | ||||
|    addWorkspace, | ||||
|    connectWorkspace, | ||||
|    selectTab, | ||||
|    newTab, | ||||
|    removeTab, | ||||
|    updateTabs | ||||
| } = workspacesStore; | ||||
|  | ||||
| const props = defineProps({ | ||||
|    connection: Object as Prop<ConnectionParams> | ||||
| }); | ||||
|  | ||||
| const hasWheelEvent = ref(false); | ||||
| const isProcessesModal = ref(false); | ||||
| const unsavedTab = ref(null); | ||||
| const tabWrap = ref(null); | ||||
|  | ||||
| const workspace = computed(() => getWorkspace(props.connection.uid)); | ||||
|  | ||||
| const draggableTabs = computed<WorkspaceTab[]>({ | ||||
|    get () { | ||||
|       return workspace.value.tabs; | ||||
|    }, | ||||
|    props: { | ||||
|       connection: Object | ||||
|    }, | ||||
|    setup () { | ||||
|       const workspacesStore = useWorkspacesStore(); | ||||
|    set (val) { | ||||
|       updateTabs({ uid: props.connection.uid, tabs: val }); | ||||
|    } | ||||
| }); | ||||
|  | ||||
|       const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); | ||||
| const isSelected = computed(() => { | ||||
|    return selectedWorkspace.value === props.connection.uid; | ||||
| }); | ||||
|  | ||||
|       const { | ||||
|          getWorkspace, | ||||
|          addWorkspace, | ||||
|          connectWorkspace, | ||||
|          removeConnected, | ||||
|          selectTab, | ||||
|          newTab, | ||||
|          removeTab, | ||||
|          updateTabs | ||||
|       } = workspacesStore; | ||||
| const selectedTab = computed(() => { | ||||
|    return workspace.value ? workspace.value.selectedTab : null; | ||||
| }); | ||||
|  | ||||
|       return { | ||||
|          selectedWorkspace, | ||||
|          getWorkspace, | ||||
|          addWorkspace, | ||||
|          connectWorkspace, | ||||
|          removeConnected, | ||||
|          selectTab, | ||||
|          newTab, | ||||
|          removeTab, | ||||
|          updateTabs | ||||
|       }; | ||||
|    }, | ||||
|    data () { | ||||
|       return { | ||||
|          hasWheelEvent: false, | ||||
|          isProcessesModal: false, | ||||
|          unsavedTab: null | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       workspace () { | ||||
|          return this.getWorkspace(this.connection.uid); | ||||
|       }, | ||||
|       draggableTabs: { | ||||
|          get () { | ||||
|             return this.workspace.tabs; | ||||
|          }, | ||||
|          set (val) { | ||||
|             this.updateTabs({ uid: this.connection.uid, tabs: val }); | ||||
|          } | ||||
|       }, | ||||
|       isSelected () { | ||||
|          return this.selectedWorkspace === this.connection.uid; | ||||
|       }, | ||||
|       isSettingSupported () { | ||||
|          if (this.workspace.breadcrumbs.table && this.workspace.customizations.tableSettings) return true; | ||||
|          if (this.workspace.breadcrumbs.view && this.workspace.customizations.viewSettings) return true; | ||||
|          if (this.workspace.breadcrumbs.trigger && this.workspace.customizations.triggerSettings) return true; | ||||
|          if (this.workspace.breadcrumbs.procedure && this.workspace.customizations.routineSettings) return true; | ||||
|          if (this.workspace.breadcrumbs.function && this.workspace.customizations.functionSettings) return true; | ||||
|          if (this.workspace.breadcrumbs.triggerFunction && this.workspace.customizations.functionSettings) return true; | ||||
|          if (this.workspace.breadcrumbs.scheduler && this.workspace.customizations.schedulerSettings) return true; | ||||
|          return false; | ||||
|       }, | ||||
|       selectedTab () { | ||||
|          return this.workspace ? this.workspace.selectedTab : null; | ||||
|       }, | ||||
|       queryTabs () { | ||||
|          return this.workspace ? this.workspace.tabs.filter(tab => tab.type === 'query') : []; | ||||
|       }, | ||||
|       schemaChild () { | ||||
|          for (const key in this.workspace.breadcrumbs) { | ||||
|             if (key === 'schema') continue; | ||||
|             if (this.workspace.breadcrumbs[key]) return this.workspace.breadcrumbs[key]; | ||||
|          } | ||||
|          return false; | ||||
|       }, | ||||
|       hasTools () { | ||||
|          if (!this.workspace.customizations) return false; | ||||
|          else { | ||||
|             return this.workspace.customizations.processesList || | ||||
|             this.workspace.customizations.usersManagement || | ||||
|             this.workspace.customizations.variables; | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
|    watch: { | ||||
|       queryTabs: { | ||||
|          handler (newVal, oldVal) { | ||||
|             if (newVal.length > oldVal.length) { | ||||
|                setTimeout(() => { | ||||
|                   const scroller = this.$refs.tabWrap; | ||||
|                   if (scroller) scroller.$el.scrollLeft = scroller.$el.scrollWidth; | ||||
|                }, 0); | ||||
|             } | ||||
|          }, | ||||
|          deep: true | ||||
|       } | ||||
|    }, | ||||
|    async created () { | ||||
|       window.addEventListener('keydown', this.onKey); | ||||
|       await this.addWorkspace(this.connection.uid); | ||||
|       const isInitiated = await Connection.checkConnection(this.connection.uid); | ||||
|       if (isInitiated) | ||||
|          this.connectWorkspace(this.connection); | ||||
|    }, | ||||
|    beforeUnmount () { | ||||
|       window.removeEventListener('keydown', this.onKey); | ||||
|    }, | ||||
|    methods: { | ||||
|       addQueryTab () { | ||||
|          this.newTab({ uid: this.connection.uid, type: 'query' }); | ||||
|       }, | ||||
|       getSelectedTab () { | ||||
|          return this.workspace.tabs.find(tab => tab.uid === this.selectedTab); | ||||
|       }, | ||||
|       onKey (e) { | ||||
|          e.stopPropagation(); | ||||
| const queryTabs = computed(() => { | ||||
|    return workspace.value ? workspace.value.tabs.filter(tab => tab.type === 'query') : []; | ||||
| }); | ||||
|  | ||||
|          if (!this.isSelected) | ||||
|             return; | ||||
| const hasTools = computed(() => { | ||||
|    if (!workspace.value.customizations) return false; | ||||
|    else { | ||||
|       return workspace.value.customizations.processesList || | ||||
|       workspace.value.customizations.usersManagement || | ||||
|       workspace.value.customizations.variables; | ||||
|    } | ||||
| }); | ||||
|  | ||||
|          if ((e.ctrlKey || e.metaKey) && e.keyCode === 84 && !e.altKey) { // CTRL|Command + t | ||||
|             this.addQueryTab(); | ||||
|          } | ||||
| watch(queryTabs, (newVal, oldVal) => { | ||||
|    if (newVal.length > oldVal.length) { | ||||
|       setTimeout(() => { | ||||
|          const scroller = tabWrap.value; | ||||
|          if (scroller) scroller.$el.scrollLeft = scroller.$el.scrollWidth; | ||||
|       }, 0); | ||||
|    } | ||||
| }); | ||||
|  | ||||
|          if ((e.ctrlKey || e.metaKey) && e.keyCode === 87 && !e.altKey) { // CTRL|Command + w | ||||
|             const currentTab = this.getSelectedTab(); | ||||
|             if (currentTab) | ||||
|                this.closeTab(currentTab); | ||||
|          } | ||||
|       }, | ||||
|       openAsPermanentTab (tab) { | ||||
|          const permanentTabs = { | ||||
|             table: 'data', | ||||
|             view: 'data', | ||||
|             trigger: 'trigger-props', | ||||
|             triggerFunction: 'trigger-function-props', | ||||
|             function: 'function-props', | ||||
|             routine: 'routine-props', | ||||
|             scheduler: 'scheduler-props' | ||||
|          }; | ||||
| const addQueryTab = () => { | ||||
|    newTab({ uid: props.connection.uid, type: 'query' }); | ||||
| }; | ||||
|  | ||||
|          this.newTab({ | ||||
|             uid: this.connection.uid, | ||||
|             schema: tab.schema, | ||||
|             elementName: tab.elementName, | ||||
|             type: permanentTabs[tab.elementType], | ||||
|             elementType: tab.elementType | ||||
|          }); | ||||
|       }, | ||||
|       closeTab (tab, force) { | ||||
|          this.unsavedTab = null; | ||||
|          // if (tab.type === 'query' && this.queryTabs.length === 1) return; | ||||
|          if (!force && tab.isChanged) { | ||||
|             this.unsavedTab = tab; | ||||
|             return; | ||||
|          } | ||||
| const getSelectedTab = () => { | ||||
|    return workspace.value.tabs.find(tab => tab.uid === selectedTab.value); | ||||
| }; | ||||
|  | ||||
|          this.removeTab({ uid: this.connection.uid, tab: tab.uid }); | ||||
|       }, | ||||
|       showProcessesModal () { | ||||
|          this.isProcessesModal = true; | ||||
|       }, | ||||
|       hideProcessesModal () { | ||||
|          this.isProcessesModal = false; | ||||
|       }, | ||||
|       addWheelEvent () { | ||||
|          if (!this.hasWheelEvent) { | ||||
|             this.$refs.tabWrap.$el.addEventListener('wheel', e => { | ||||
|                if (e.deltaY > 0) this.$refs.tabWrap.$el.scrollLeft += 50; | ||||
|                else this.$refs.tabWrap.$el.scrollLeft -= 50; | ||||
|             }); | ||||
|             this.hasWheelEvent = true; | ||||
|          } | ||||
|       }, | ||||
|       cutText (string) { | ||||
|          const limit = 20; | ||||
|          const escapedString = string.replace(/\s{2,}/g, ' '); | ||||
|          if (escapedString.length > limit) | ||||
|             return `${escapedString.substr(0, limit)}...`; | ||||
|          return escapedString; | ||||
|       } | ||||
| const onKey = (e: KeyboardEvent) => { | ||||
|    e.stopPropagation(); | ||||
|  | ||||
|    if (!isSelected.value) | ||||
|       return; | ||||
|  | ||||
|    if ((e.ctrlKey || e.metaKey) && e.key === 't' && !e.altKey) { // CTRL|Command + t | ||||
|       addQueryTab(); | ||||
|    } | ||||
|  | ||||
|    if ((e.ctrlKey || e.metaKey) && e.key === 'w' && !e.altKey) { // CTRL|Command + w | ||||
|       const currentTab = getSelectedTab(); | ||||
|       if (currentTab) | ||||
|          closeTab(currentTab); | ||||
|    } | ||||
| }; | ||||
|  | ||||
| const openAsPermanentTab = (tab: WorkspaceTab) => { | ||||
|    const permanentTabs = { | ||||
|       table: 'data', | ||||
|       view: 'data', | ||||
|       trigger: 'trigger-props', | ||||
|       triggerFunction: 'trigger-function-props', | ||||
|       function: 'function-props', | ||||
|       routine: 'routine-props', | ||||
|       procedure: 'routine-props', | ||||
|       scheduler: 'scheduler-props' | ||||
|    } as {[key: string]: string}; | ||||
|  | ||||
|    newTab({ | ||||
|       uid: props.connection.uid, | ||||
|       schema: tab.schema, | ||||
|       elementName: tab.elementName, | ||||
|       type: permanentTabs[tab.elementType], | ||||
|       elementType: tab.elementType | ||||
|    }); | ||||
| }; | ||||
|  | ||||
| const closeTab = (tab: WorkspaceTab, force = false) => { | ||||
|    unsavedTab.value = null; | ||||
|    // if (tab.type === 'query' && this.queryTabs.length === 1) return; | ||||
|    if (!force && tab.isChanged) { | ||||
|       unsavedTab.value = tab; | ||||
|       return; | ||||
|    } | ||||
|  | ||||
|    removeTab({ uid: props.connection.uid, tab: tab.uid }); | ||||
| }; | ||||
|  | ||||
| const showProcessesModal = () => { | ||||
|    isProcessesModal.value = true; | ||||
| }; | ||||
|  | ||||
| const hideProcessesModal = () => { | ||||
|    isProcessesModal.value = false; | ||||
| }; | ||||
|  | ||||
| const addWheelEvent = () => { | ||||
|    if (!hasWheelEvent.value) { | ||||
|       tabWrap.value.$el.addEventListener('wheel', (e: WheelEvent) => { | ||||
|          if (e.deltaY > 0) tabWrap.value.$el.scrollLeft += 50; | ||||
|          else tabWrap.value.$el.scrollLeft -= 50; | ||||
|       }); | ||||
|       hasWheelEvent.value = true; | ||||
|    } | ||||
| }; | ||||
|  | ||||
| const cutText = (string: string) => { | ||||
|    const limit = 20; | ||||
|    const escapedString = string.replace(/\s{2,}/g, ' '); | ||||
|    if (escapedString.length > limit) | ||||
|       return `${escapedString.substr(0, limit)}...`; | ||||
|    return escapedString; | ||||
| }; | ||||
|  | ||||
| (async () => { | ||||
|    window.addEventListener('keydown', onKey); | ||||
|    await addWorkspace(props.connection.uid); | ||||
|    const isInitiated = await Connection.checkConnection(props.connection.uid); | ||||
|    if (isInitiated) | ||||
|       connectWorkspace(props.connection); | ||||
| })(); | ||||
|  | ||||
| onBeforeUnmount(() => { | ||||
|    window.removeEventListener('keydown', onKey); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
|   | ||||
| @@ -8,23 +8,23 @@ | ||||
|                   :class="{'active': selectedTab === 'general'}" | ||||
|                   @click="selectTab('general')" | ||||
|                > | ||||
|                   <a class="tab-link">{{ $t('word.general') }}</a> | ||||
|                   <a class="tab-link">{{ t('word.general') }}</a> | ||||
|                </li> | ||||
|                <li | ||||
|                   v-if="customizations.sslConnection" | ||||
|                   v-if="clientCustomizations.sslConnection" | ||||
|                   class="tab-item c-hand" | ||||
|                   :class="{'active': selectedTab === 'ssl'}" | ||||
|                   @click="selectTab('ssl')" | ||||
|                > | ||||
|                   <a class="tab-link">{{ $t('word.ssl') }}</a> | ||||
|                   <a class="tab-link">{{ t('word.ssl') }}</a> | ||||
|                </li> | ||||
|                <li | ||||
|                   v-if="customizations.sshConnection" | ||||
|                   v-if="clientCustomizations.sshConnection" | ||||
|                   class="tab-item c-hand" | ||||
|                   :class="{'active': selectedTab === 'ssh'}" | ||||
|                   @click="selectTab('ssh')" | ||||
|                > | ||||
|                   <a class="tab-link">{{ $t('word.sshTunnel') }}</a> | ||||
|                   <a class="tab-link">{{ t('word.sshTunnel') }}</a> | ||||
|                </li> | ||||
|             </ul> | ||||
|          </div> | ||||
| @@ -34,7 +34,7 @@ | ||||
|                   <fieldset class="m-0" :disabled="isBusy"> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.connectionName') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.connectionName') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -47,7 +47,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.client') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.client') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseSelect | ||||
| @@ -61,7 +61,7 @@ | ||||
|                      </div> | ||||
|                      <div v-if="connection.client === 'pg'" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.connectionString') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.connectionString') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -72,9 +72,9 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.hostName') }}/IP</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -84,22 +84,22 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.database') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.database') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="connection.databasePath" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('databasePath')" | ||||
|                               @change="pathSelection($event, 'databasePath')" | ||||
|                            /> | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.port') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.port') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -111,9 +111,9 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="customizations.database" class="form-group columns"> | ||||
|                      <div v-if="clientCustomizations.database" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.database') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.database') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -123,9 +123,9 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.user') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.user') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -136,9 +136,9 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.password') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.password') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -149,32 +149,32 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="customizations.connectionSchema" class="form-group columns"> | ||||
|                      <div v-if="clientCustomizations.connectionSchema" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.schema') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.schema') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
|                               v-model="connection.schema" | ||||
|                               class="form-input" | ||||
|                               type="text" | ||||
|                               :placeholder="$t('word.all')" | ||||
|                               :placeholder="t('word.all')" | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="customizations.readOnlyMode" class="form-group columns"> | ||||
|                      <div v-if="clientCustomizations.readOnlyMode" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12" /> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <label class="form-checkbox form-inline"> | ||||
|                               <input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ $t('message.readOnlyMode') }} | ||||
|                               <input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('message.readOnlyMode') }} | ||||
|                            </label> | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12" /> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <label class="form-checkbox form-inline"> | ||||
|                               <input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }} | ||||
|                               <input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('message.askCredentials') }} | ||||
|                            </label> | ||||
|                         </div> | ||||
|                      </div> | ||||
| @@ -188,7 +188,7 @@ | ||||
|                   <div class="form-group columns"> | ||||
|                      <div class="column col-4 col-sm-12"> | ||||
|                         <label class="form-label cut-text"> | ||||
|                            {{ $t('message.enableSsl') }} | ||||
|                            {{ t('message.enableSsl') }} | ||||
|                         </label> | ||||
|                      </div> | ||||
|                      <div class="column col-8 col-sm-12"> | ||||
| @@ -201,12 +201,12 @@ | ||||
|                   <fieldset class="m-0" :disabled="isBusy || !connection.ssl"> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.privateKey') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.privateKey') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="connection.key" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('key')" | ||||
|                               @change="pathSelection($event, 'key')" | ||||
|                            /> | ||||
| @@ -214,12 +214,12 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.certificate') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.certificate') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="connection.cert" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('cert')" | ||||
|                               @change="pathSelection($event, 'cert')" | ||||
|                            /> | ||||
| @@ -227,12 +227,12 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.caCertificate') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.caCertificate') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="connection.ca" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('ca')" | ||||
|                               @change="pathSelection($event, 'ca')" | ||||
|                            /> | ||||
| @@ -240,7 +240,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.ciphers') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.ciphers') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -255,7 +255,7 @@ | ||||
|                         <div class="column col-4 col-sm-12" /> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <label class="form-checkbox form-inline"> | ||||
|                               <input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ $t('message.untrustedConnection') }} | ||||
|                               <input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('message.untrustedConnection') }} | ||||
|                            </label> | ||||
|                         </div> | ||||
|                      </div> | ||||
| @@ -269,7 +269,7 @@ | ||||
|                   <div class="form-group columns"> | ||||
|                      <div class="column col-4 col-sm-12"> | ||||
|                         <label class="form-label cut-text"> | ||||
|                            {{ $t('message.enableSsh') }} | ||||
|                            {{ t('message.enableSsh') }} | ||||
|                         </label> | ||||
|                      </div> | ||||
|                      <div class="column col-8 col-sm-12"> | ||||
| @@ -282,7 +282,7 @@ | ||||
|                   <fieldset class="m-0" :disabled="isBusy || !connection.ssh"> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.hostName') }}/IP</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -294,7 +294,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.user') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.user') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -306,7 +306,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.password') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.password') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -318,7 +318,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.port') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.port') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -332,12 +332,12 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.privateKey') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.privateKey') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="connection.sshKey" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('sshKey')" | ||||
|                               @change="pathSelection($event, 'sshKey')" | ||||
|                            /> | ||||
| @@ -345,7 +345,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.passphrase') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.passphrase') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -368,7 +368,7 @@ | ||||
|                @click="startTest" | ||||
|             > | ||||
|                <i class="mdi mdi-24px mdi-lightning-bolt mr-1" /> | ||||
|                {{ $t('message.testConnection') }} | ||||
|                {{ t('message.testConnection') }} | ||||
|             </button> | ||||
|             <button | ||||
|                id="connection-save" | ||||
| @@ -377,7 +377,7 @@ | ||||
|                @click="saveConnection" | ||||
|             > | ||||
|                <i class="mdi mdi-24px mdi-content-save mr-1" /> | ||||
|                {{ $t('word.save') }} | ||||
|                {{ t('word.save') }} | ||||
|             </button> | ||||
|          </div> | ||||
|       </div> | ||||
| @@ -389,188 +389,171 @@ | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| <script setup lang="ts"> | ||||
| import { computed, Ref, ref, watch } from 'vue'; | ||||
| import customizations from 'common/customizations'; | ||||
| import Connection from '@/ipc-api/Connection'; | ||||
| import { uidGen } from 'common/libs/uidGen'; | ||||
| import { useConnectionsStore } from '@/stores/connections'; | ||||
| import { useNotificationsStore } from '@/stores/notifications'; | ||||
| import { useWorkspacesStore } from '@/stores/workspaces'; | ||||
| import ModalAskCredentials from '@/components/ModalAskCredentials'; | ||||
| import BaseUploadInput from '@/components/BaseUploadInput'; | ||||
| import ModalAskCredentials from '@/components/ModalAskCredentials.vue'; | ||||
| import BaseUploadInput from '@/components/BaseUploadInput.vue'; | ||||
| import BaseSelect from '@/components/BaseSelect.vue'; | ||||
| import { ConnectionParams } from 'common/interfaces/antares'; | ||||
| import { useI18n } from 'vue-i18n'; | ||||
|  | ||||
| export default { | ||||
|    name: 'WorkspaceAddConnectionPanel', | ||||
|    components: { | ||||
|       ModalAskCredentials, | ||||
|       BaseUploadInput, | ||||
|       BaseSelect | ||||
|    }, | ||||
|    setup () { | ||||
|       const { addConnection } = useConnectionsStore(); | ||||
|       const { addNotification } = useNotificationsStore(); | ||||
|       const workspacesStore = useWorkspacesStore(); | ||||
| const { t } = useI18n(); | ||||
|  | ||||
|       const { connectWorkspace, selectWorkspace } = workspacesStore; | ||||
| const { addConnection } = useConnectionsStore(); | ||||
| const { addNotification } = useNotificationsStore(); | ||||
| const workspacesStore = useWorkspacesStore(); | ||||
|  | ||||
|       return { | ||||
|          addConnection, | ||||
|          addNotification, | ||||
|          connectWorkspace, | ||||
|          selectWorkspace | ||||
|       }; | ||||
|    }, | ||||
|    data () { | ||||
|       return { | ||||
|          clients: [ | ||||
|             { name: 'MySQL', slug: 'mysql' }, | ||||
|             { name: 'MariaDB', slug: 'maria' }, | ||||
|             { name: 'PostgreSQL', slug: 'pg' }, | ||||
|             { name: 'SQLite', slug: 'sqlite' } | ||||
|          ], | ||||
|          connection: { | ||||
|             name: '', | ||||
|             client: 'mysql', | ||||
|             host: '127.0.0.1', | ||||
|             database: null, | ||||
|             databasePath: '', | ||||
|             port: null, | ||||
|             user: null, | ||||
|             password: '', | ||||
|             ask: false, | ||||
|             readonly: false, | ||||
|             uid: uidGen('C'), | ||||
|             ssl: false, | ||||
|             cert: '', | ||||
|             key: '', | ||||
|             ca: '', | ||||
|             ciphers: '', | ||||
|             untrustedConnection: false, | ||||
|             ssh: false, | ||||
|             sshHost: '', | ||||
|             sshUser: '', | ||||
|             sshPass: '', | ||||
|             sshKey: '', | ||||
|             sshPort: 22, | ||||
|             pgConnString: '' | ||||
|          }, | ||||
|          isConnecting: false, | ||||
|          isTesting: false, | ||||
|          isAsking: false, | ||||
|          selectedTab: 'general' | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       customizations () { | ||||
|          return customizations[this.connection.client]; | ||||
|       }, | ||||
|       isBusy () { | ||||
|          return this.isConnecting || this.isTesting; | ||||
| const { connectWorkspace, selectWorkspace } = workspacesStore; | ||||
|  | ||||
| const clients = ref([ | ||||
|    { name: 'MySQL', slug: 'mysql' }, | ||||
|    { name: 'MariaDB', slug: 'maria' }, | ||||
|    { name: 'PostgreSQL', slug: 'pg' }, | ||||
|    { name: 'SQLite', slug: 'sqlite' } | ||||
| ]); | ||||
|  | ||||
| const connection = ref({ | ||||
|    name: '', | ||||
|    client: 'mysql', | ||||
|    host: '127.0.0.1', | ||||
|    database: null, | ||||
|    databasePath: '', | ||||
|    port: null, | ||||
|    user: null, | ||||
|    password: '', | ||||
|    ask: false, | ||||
|    readonly: false, | ||||
|    uid: uidGen('C'), | ||||
|    ssl: false, | ||||
|    cert: '', | ||||
|    key: '', | ||||
|    ca: '', | ||||
|    ciphers: '', | ||||
|    untrustedConnection: false, | ||||
|    ssh: false, | ||||
|    sshHost: '', | ||||
|    sshUser: '', | ||||
|    sshPass: '', | ||||
|    sshKey: '', | ||||
|    sshPort: 22, | ||||
|    pgConnString: '' | ||||
| }) as Ref<ConnectionParams & { pgConnString: string }>; | ||||
|  | ||||
| const firstInput: Ref<HTMLInputElement> = ref(null); | ||||
| const isConnecting = ref(false); | ||||
| const isTesting = ref(false); | ||||
| const isAsking = ref(false); | ||||
| const selectedTab = ref('general'); | ||||
|  | ||||
| const clientCustomizations = computed(() => { | ||||
|    return customizations[connection.value.client]; | ||||
| }); | ||||
|  | ||||
| const isBusy = computed(() => { | ||||
|    return isConnecting.value || isTesting.value; | ||||
| }); | ||||
|  | ||||
| watch(() => connection.value.client, () => { | ||||
|    connection.value.user = clientCustomizations.value.defaultUser; | ||||
|    connection.value.port = clientCustomizations.value.defaultPort; | ||||
|    connection.value.database = clientCustomizations.value.defaultDatabase; | ||||
| }); | ||||
|  | ||||
| const setDefaults = () => { | ||||
|    connection.value.user = clientCustomizations.value.defaultUser; | ||||
|    connection.value.port = clientCustomizations.value.defaultPort; | ||||
|    connection.value.database = clientCustomizations.value.defaultDatabase; | ||||
| }; | ||||
|  | ||||
| const startTest = async () => { | ||||
|    isTesting.value = true; | ||||
|  | ||||
|    if (connection.value.ask) | ||||
|       isAsking.value = true; | ||||
|    else { | ||||
|       try { | ||||
|          const res = await Connection.makeTest(connection); | ||||
|          if (res.status === 'error') | ||||
|             addNotification({ status: 'error', message: res.response.message || res.response.toString() }); | ||||
|          else | ||||
|             addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') }); | ||||
|       } | ||||
|    }, | ||||
|    watch: { | ||||
|       'connection.client' () { | ||||
|          this.connection.user = this.customizations.defaultUser; | ||||
|          this.connection.port = this.customizations.defaultPort; | ||||
|          this.connection.database = this.customizations.defaultDatabase; | ||||
|       catch (err) { | ||||
|          addNotification({ status: 'error', message: err.stack }); | ||||
|       } | ||||
|    }, | ||||
|    created () { | ||||
|       this.setDefaults(); | ||||
|  | ||||
|       setTimeout(() => { | ||||
|          if (this.$refs.firstInput) this.$refs.firstInput.focus(); | ||||
|       }, 20); | ||||
|    }, | ||||
|    methods: { | ||||
|       setDefaults () { | ||||
|          this.connection.user = this.customizations.defaultUser; | ||||
|          this.connection.port = this.customizations.defaultPort; | ||||
|          this.connection.database = this.customizations.defaultDatabase; | ||||
|       }, | ||||
|       async startConnection () { | ||||
|          await this.saveConnection(); | ||||
|          this.isConnecting = true; | ||||
|  | ||||
|          if (this.connection.ask) | ||||
|             this.isAsking = true; | ||||
|          else { | ||||
|             await this.connectWorkspace(this.connection); | ||||
|             this.isConnecting = false; | ||||
|          } | ||||
|       }, | ||||
|       async startTest () { | ||||
|          this.isTesting = true; | ||||
|  | ||||
|          if (this.connection.ask) | ||||
|             this.isAsking = true; | ||||
|          else { | ||||
|             try { | ||||
|                const res = await Connection.makeTest(this.connection); | ||||
|                if (res.status === 'error') | ||||
|                   this.addNotification({ status: 'error', message: res.response.message || res.response.toString() }); | ||||
|                else | ||||
|                   this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') }); | ||||
|             } | ||||
|             catch (err) { | ||||
|                this.addNotification({ status: 'error', message: err.stack }); | ||||
|             } | ||||
|  | ||||
|             this.isTesting = false; | ||||
|          } | ||||
|       }, | ||||
|       async continueTest (credentials) { // if "Ask for credentials" is true | ||||
|          this.isAsking = false; | ||||
|          const params = Object.assign({}, this.connection, credentials); | ||||
|          try { | ||||
|             if (this.isConnecting) { | ||||
|                const params = Object.assign({}, this.connection, credentials); | ||||
|                await this.connectWorkspace(params); | ||||
|                this.isConnecting = false; | ||||
|             } | ||||
|             else { | ||||
|                const res = await Connection.makeTest(params); | ||||
|                if (res.status === 'error') | ||||
|                   this.addNotification({ status: 'error', message: res.response.message || res.response.toString() }); | ||||
|                else | ||||
|                   this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') }); | ||||
|             } | ||||
|          } | ||||
|          catch (err) { | ||||
|             this.addNotification({ status: 'error', message: err.stack }); | ||||
|          } | ||||
|  | ||||
|          this.isTesting = false; | ||||
|       }, | ||||
|       saveConnection () { | ||||
|          this.selectWorkspace(this.connection.uid); | ||||
|          return this.addConnection(this.connection); | ||||
|       }, | ||||
|       closeAsking () { | ||||
|          this.isTesting = false; | ||||
|          this.isAsking = false; | ||||
|       }, | ||||
|       selectTab (tab) { | ||||
|          this.selectedTab = tab; | ||||
|       }, | ||||
|       toggleSsl () { | ||||
|          this.connection.ssl = !this.connection.ssl; | ||||
|       }, | ||||
|       toggleSsh () { | ||||
|          this.connection.ssh = !this.connection.ssh; | ||||
|       }, | ||||
|       pathSelection (event, name) { | ||||
|          const { files } = event.target; | ||||
|          if (!files.length) return; | ||||
|  | ||||
|          this.connection[name] = files[0].path; | ||||
|       }, | ||||
|       pathClear (name) { | ||||
|          this.connection[name] = ''; | ||||
|       } | ||||
|       isTesting.value = false; | ||||
|    } | ||||
| }; | ||||
|  | ||||
| const continueTest = async (credentials: { user: string; password: string }) => { // if "Ask for credentials" is true | ||||
|    isAsking.value = false; | ||||
|    const params = Object.assign({}, connection.value, credentials); | ||||
|  | ||||
|    try { | ||||
|       if (isConnecting.value) { | ||||
|          await connectWorkspace(params); | ||||
|          isConnecting.value = false; | ||||
|       } | ||||
|       else { | ||||
|          const res = await Connection.makeTest(params); | ||||
|          if (res.status === 'error') | ||||
|             addNotification({ status: 'error', message: res.response.message || res.response.toString() }); | ||||
|          else | ||||
|             addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') }); | ||||
|       } | ||||
|    } | ||||
|    catch (err) { | ||||
|       addNotification({ status: 'error', message: err.stack }); | ||||
|    } | ||||
|  | ||||
|    isTesting.value = false; | ||||
| }; | ||||
|  | ||||
| const saveConnection = () => { | ||||
|    selectWorkspace(connection.value.uid); | ||||
|    return addConnection(connection.value); | ||||
| }; | ||||
|  | ||||
| const closeAsking = () => { | ||||
|    isTesting.value = false; | ||||
|    isAsking.value = false; | ||||
| }; | ||||
|  | ||||
| const selectTab = (tab: string) => { | ||||
|    selectedTab.value = tab; | ||||
| }; | ||||
|  | ||||
| const toggleSsl = () => { | ||||
|    connection.value.ssl = !connection.value.ssl; | ||||
| }; | ||||
|  | ||||
| const toggleSsh = () => { | ||||
|    connection.value.ssh = !connection.value.ssh; | ||||
| }; | ||||
|  | ||||
| const pathSelection = (event: Event & {target: {files: {path: string}[]}}, name: keyof ConnectionParams) => { | ||||
|    const { files } = event.target; | ||||
|    if (!files.length) return; | ||||
|  | ||||
|    (connection.value as unknown as {[key: string]: string})[name] = files[0].path as string; | ||||
| }; | ||||
|  | ||||
| const pathClear = (name: keyof ConnectionParams) => { | ||||
|    (connection.value as unknown as {[key: string]: string})[name] = ''; | ||||
| }; | ||||
|  | ||||
| setDefaults(); | ||||
|  | ||||
| setTimeout(() => { | ||||
|    if (firstInput.value) firstInput.value.focus(); | ||||
| }, 20); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
| @@ -8,23 +8,23 @@ | ||||
|                   :class="{'active': selectedTab === 'general'}" | ||||
|                   @click="selectTab('general')" | ||||
|                > | ||||
|                   <a class="tab-link">{{ $t('word.general') }}</a> | ||||
|                   <a class="tab-link">{{ t('word.general') }}</a> | ||||
|                </li> | ||||
|                <li | ||||
|                   v-if="customizations.sslConnection" | ||||
|                   v-if="clientCustomizations.sslConnection" | ||||
|                   class="tab-item c-hand" | ||||
|                   :class="{'active': selectedTab === 'ssl'}" | ||||
|                   @click="selectTab('ssl')" | ||||
|                > | ||||
|                   <a class="tab-link">{{ $t('word.ssl') }}</a> | ||||
|                   <a class="tab-link">{{ t('word.ssl') }}</a> | ||||
|                </li> | ||||
|                <li | ||||
|                   v-if="customizations.sshConnection" | ||||
|                   v-if="clientCustomizations.sshConnection" | ||||
|                   class="tab-item c-hand" | ||||
|                   :class="{'active': selectedTab === 'ssh'}" | ||||
|                   @click="selectTab('ssh')" | ||||
|                > | ||||
|                   <a class="tab-link">{{ $t('word.sshTunnel') }}</a> | ||||
|                   <a class="tab-link">{{ t('word.sshTunnel') }}</a> | ||||
|                </li> | ||||
|             </ul> | ||||
|          </div> | ||||
| @@ -34,7 +34,7 @@ | ||||
|                   <fieldset class="m-0" :disabled="isBusy"> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.connectionName') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.connectionName') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -47,7 +47,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.client') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.client') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseSelect | ||||
| @@ -63,7 +63,7 @@ | ||||
|                      </div> | ||||
|                      <div v-if="connection.client === 'pg'" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.connectionString') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.connectionString') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -74,9 +74,9 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.hostName') }}/IP</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -86,22 +86,22 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.database') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.database') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="localConnection.databasePath" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('databasePath')" | ||||
|                               @change="pathSelection($event, 'databasePath')" | ||||
|                            /> | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.port') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.port') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -113,9 +113,9 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="customizations.database" class="form-group columns"> | ||||
|                      <div v-if="clientCustomizations.database" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.database') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.database') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -125,9 +125,9 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.user') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.user') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -138,9 +138,9 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.password') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.password') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -151,32 +151,32 @@ | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="customizations.connectionSchema" class="form-group columns"> | ||||
|                      <div v-if="clientCustomizations.connectionSchema" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.schema') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.schema') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
|                               v-model="localConnection.schema" | ||||
|                               class="form-input" | ||||
|                               type="text" | ||||
|                               :placeholder="$t('word.all')" | ||||
|                               :placeholder="t('word.all')" | ||||
|                            > | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="customizations.readOnlyMode" class="form-group columns"> | ||||
|                      <div v-if="clientCustomizations.readOnlyMode" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12" /> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <label class="form-checkbox form-inline"> | ||||
|                               <input v-model="localConnection.readonly" type="checkbox"><i class="form-icon" /> {{ $t('message.readOnlyMode') }} | ||||
|                               <input v-model="localConnection.readonly" type="checkbox"><i class="form-icon" /> {{ t('message.readOnlyMode') }} | ||||
|                            </label> | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div v-if="!customizations.fileConnection" class="form-group columns"> | ||||
|                      <div v-if="!clientCustomizations.fileConnection" class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12" /> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <label class="form-checkbox form-inline"> | ||||
|                               <input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }} | ||||
|                               <input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ t('message.askCredentials') }} | ||||
|                            </label> | ||||
|                         </div> | ||||
|                      </div> | ||||
| @@ -190,7 +190,7 @@ | ||||
|                   <div class="form-group columns"> | ||||
|                      <div class="column col-4 col-sm-12"> | ||||
|                         <label class="form-label cut-text"> | ||||
|                            {{ $t('message.enableSsl') }} | ||||
|                            {{ t('message.enableSsl') }} | ||||
|                         </label> | ||||
|                      </div> | ||||
|                      <div class="column col-8 col-sm-12"> | ||||
| @@ -203,12 +203,12 @@ | ||||
|                   <fieldset class="m-0" :disabled="isBusy || !localConnection.ssl"> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.privateKey') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.privateKey') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="localConnection.key" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('key')" | ||||
|                               @change="pathSelection($event, 'key')" | ||||
|                            /> | ||||
| @@ -216,12 +216,12 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.certificate') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.certificate') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="localConnection.cert" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('cert')" | ||||
|                               @change="pathSelection($event, 'cert')" | ||||
|                            /> | ||||
| @@ -229,12 +229,12 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.caCertificate') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.caCertificate') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="localConnection.ca" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('ca')" | ||||
|                               @change="pathSelection($event, 'ca')" | ||||
|                            /> | ||||
| @@ -242,7 +242,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.ciphers') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.ciphers') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -263,7 +263,7 @@ | ||||
|                   <div class="form-group columns"> | ||||
|                      <div class="column col-4 col-sm-12"> | ||||
|                         <label class="form-label cut-text"> | ||||
|                            {{ $t('message.enableSsh') }} | ||||
|                            {{ t('message.enableSsh') }} | ||||
|                         </label> | ||||
|                      </div> | ||||
|                      <div class="column col-8 col-sm-12"> | ||||
| @@ -276,7 +276,7 @@ | ||||
|                   <fieldset class="m-0" :disabled="isBusy || !localConnection.ssh"> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.hostName') }}/IP</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -288,7 +288,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.user') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.user') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -300,7 +300,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.password') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.password') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -312,7 +312,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.port') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.port') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -326,12 +326,12 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.privateKey') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.privateKey') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <BaseUploadInput | ||||
|                               :model-value="localConnection.sshKey" | ||||
|                               :message="$t('word.browse')" | ||||
|                               :message="t('word.browse')" | ||||
|                               @clear="pathClear('sshKey')" | ||||
|                               @change="pathSelection($event, 'sshKey')" | ||||
|                            /> | ||||
| @@ -339,7 +339,7 @@ | ||||
|                      </div> | ||||
|                      <div class="form-group columns"> | ||||
|                         <div class="column col-4 col-sm-12"> | ||||
|                            <label class="form-label cut-text">{{ $t('word.passphrase') }}</label> | ||||
|                            <label class="form-label cut-text">{{ t('word.passphrase') }}</label> | ||||
|                         </div> | ||||
|                         <div class="column col-8 col-sm-12"> | ||||
|                            <input | ||||
| @@ -362,7 +362,7 @@ | ||||
|                @click="startTest" | ||||
|             > | ||||
|                <i class="mdi mdi-24px mdi-lightning-bolt mr-1" /> | ||||
|                {{ $t('message.testConnection') }} | ||||
|                {{ t('message.testConnection') }} | ||||
|             </button> | ||||
|             <button | ||||
|                id="connection-save" | ||||
| @@ -371,7 +371,7 @@ | ||||
|                @click="saveConnection" | ||||
|             > | ||||
|                <i class="mdi mdi-24px mdi-content-save mr-1" /> | ||||
|                {{ $t('word.save') }} | ||||
|                {{ t('word.save') }} | ||||
|             </button> | ||||
|             <button | ||||
|                id="connection-connect" | ||||
| @@ -381,7 +381,7 @@ | ||||
|                @click="startConnection" | ||||
|             > | ||||
|                <i class="mdi mdi-24px mdi-connection mr-1" /> | ||||
|                {{ $t('word.connect') }} | ||||
|                {{ t('word.connect') }} | ||||
|             </button> | ||||
|          </div> | ||||
|       </div> | ||||
| @@ -393,154 +393,150 @@ | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| <script setup lang="ts"> | ||||
| import { computed, Prop, Ref, ref, watch } from 'vue'; | ||||
| import customizations from 'common/customizations'; | ||||
| import { useConnectionsStore } from '@/stores/connections'; | ||||
| import { useNotificationsStore } from '@/stores/notifications'; | ||||
| import { useWorkspacesStore } from '@/stores/workspaces'; | ||||
| import Connection from '@/ipc-api/Connection'; | ||||
| import ModalAskCredentials from '@/components/ModalAskCredentials'; | ||||
| import BaseUploadInput from '@/components/BaseUploadInput'; | ||||
| import ModalAskCredentials from '@/components/ModalAskCredentials.vue'; | ||||
| import BaseUploadInput from '@/components/BaseUploadInput.vue'; | ||||
| import BaseSelect from '@/components/BaseSelect.vue'; | ||||
| import { ConnectionParams } from 'common/interfaces/antares'; | ||||
| import { useI18n } from 'vue-i18n'; | ||||
|  | ||||
| export default { | ||||
|    name: 'WorkspaceEditConnectionPanel', | ||||
|    components: { | ||||
|       ModalAskCredentials, | ||||
|       BaseUploadInput, | ||||
|       BaseSelect | ||||
|    }, | ||||
|    props: { | ||||
|       connection: Object | ||||
|    }, | ||||
|    setup () { | ||||
|       const { editConnection } = useConnectionsStore(); | ||||
|       const { addNotification } = useNotificationsStore(); | ||||
|       const { connectWorkspace } = useWorkspacesStore(); | ||||
| const { t } = useI18n(); | ||||
|  | ||||
|       return { | ||||
|          editConnection, | ||||
|          addNotification, | ||||
|          connectWorkspace | ||||
|       }; | ||||
|    }, | ||||
|    data () { | ||||
|       return { | ||||
|          clients: [ | ||||
|             { name: 'MySQL', slug: 'mysql' }, | ||||
|             { name: 'MariaDB', slug: 'maria' }, | ||||
|             { name: 'PostgreSQL', slug: 'pg' }, | ||||
|             { name: 'SQLite', slug: 'sqlite' } | ||||
|          ], | ||||
|          isConnecting: false, | ||||
|          isTesting: false, | ||||
|          isAsking: false, | ||||
|          localConnection: null, | ||||
|          selectedTab: 'general' | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       customizations () { | ||||
|          return customizations[this.localConnection.client]; | ||||
|       }, | ||||
|       isBusy () { | ||||
|          return this.isConnecting || this.isTesting; | ||||
|       }, | ||||
|       hasChanges () { | ||||
|          return JSON.stringify(this.connection) !== JSON.stringify(this.localConnection); | ||||
|       } | ||||
|    }, | ||||
|    watch: { | ||||
|       connection () { | ||||
|          this.localConnection = JSON.parse(JSON.stringify(this.connection)); | ||||
|       } | ||||
|    }, | ||||
|    created () { | ||||
|       this.localConnection = JSON.parse(JSON.stringify(this.connection)); | ||||
|    }, | ||||
|    methods: { | ||||
|       async startConnection () { | ||||
|          await this.saveConnection(); | ||||
|          this.isConnecting = true; | ||||
| const props = defineProps({ | ||||
|    connection: Object as Prop<ConnectionParams> | ||||
| }); | ||||
|  | ||||
|          if (this.localConnection.ask) | ||||
|             this.isAsking = true; | ||||
|          else { | ||||
|             await this.connectWorkspace(this.localConnection); | ||||
|             this.isConnecting = false; | ||||
|          } | ||||
|       }, | ||||
|       async startTest () { | ||||
|          this.isTesting = true; | ||||
| const { editConnection } = useConnectionsStore(); | ||||
| const { addNotification } = useNotificationsStore(); | ||||
| const { connectWorkspace } = useWorkspacesStore(); | ||||
|  | ||||
|          if (this.localConnection.ask) | ||||
|             this.isAsking = true; | ||||
|          else { | ||||
|             try { | ||||
|                const res = await Connection.makeTest(this.localConnection); | ||||
|                if (res.status === 'error') | ||||
|                   this.addNotification({ status: 'error', message: res.response.message || res.response.toString() }); | ||||
|                else | ||||
|                   this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') }); | ||||
|             } | ||||
|             catch (err) { | ||||
|                this.addNotification({ status: 'error', message: err.stack }); | ||||
|             } | ||||
| const clients = ref([ | ||||
|    { name: 'MySQL', slug: 'mysql' }, | ||||
|    { name: 'MariaDB', slug: 'maria' }, | ||||
|    { name: 'PostgreSQL', slug: 'pg' }, | ||||
|    { name: 'SQLite', slug: 'sqlite' } | ||||
| ]); | ||||
|  | ||||
|             this.isTesting = false; | ||||
|          } | ||||
|       }, | ||||
|       async continueTest (credentials) { // if "Ask for credentials" is true | ||||
|          this.isAsking = false; | ||||
|          const params = Object.assign({}, this.localConnection, credentials); | ||||
|          try { | ||||
|             if (this.isConnecting) { | ||||
|                const params = Object.assign({}, this.connection, credentials); | ||||
|                await this.connectWorkspace(params); | ||||
|                this.isConnecting = false; | ||||
|             } | ||||
|             else { | ||||
|                const res = await Connection.makeTest(params); | ||||
|                if (res.status === 'error') | ||||
|                   this.addNotification({ status: 'error', message: res.response.message || res.response.toString() }); | ||||
|                else | ||||
|                   this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') }); | ||||
|             } | ||||
|          } | ||||
|          catch (err) { | ||||
|             this.addNotification({ status: 'error', message: err.stack }); | ||||
|          } | ||||
| const firstInput: Ref<HTMLInputElement> = ref(null); | ||||
| const localConnection: Ref<ConnectionParams & { pgConnString: string }> = ref(null); | ||||
| const isConnecting = ref(false); | ||||
| const isTesting = ref(false); | ||||
| const isAsking = ref(false); | ||||
| const selectedTab = ref('general'); | ||||
|  | ||||
|          this.isTesting = false; | ||||
|       }, | ||||
|       saveConnection () { | ||||
|          return this.editConnection(this.localConnection); | ||||
|       }, | ||||
|       closeAsking () { | ||||
|          this.isTesting = false; | ||||
|          this.isAsking = false; | ||||
|          this.isConnecting = false; | ||||
|       }, | ||||
|       selectTab (tab) { | ||||
|          this.selectedTab = tab; | ||||
|       }, | ||||
|       toggleSsl () { | ||||
|          this.localConnection.ssl = !this.localConnection.ssl; | ||||
|       }, | ||||
|       toggleSsh () { | ||||
|          this.localConnection.ssh = !this.localConnection.ssh; | ||||
|       }, | ||||
|       pathSelection (event, name) { | ||||
|          const { files } = event.target; | ||||
|          if (!files.length) return; | ||||
| const clientCustomizations = computed(() => { | ||||
|    return customizations[localConnection.value.client]; | ||||
| }); | ||||
|  | ||||
|          this.localConnection[name] = files[0].path; | ||||
|       }, | ||||
|       pathClear (name) { | ||||
|          this.localConnection[name] = ''; | ||||
|       } | ||||
| const isBusy = computed(() => { | ||||
|    return isConnecting.value || isTesting.value; | ||||
| }); | ||||
|  | ||||
| const hasChanges = computed(() => { | ||||
|    return JSON.stringify(props.connection) !== JSON.stringify(localConnection.value); | ||||
| }); | ||||
|  | ||||
| watch(() => props.connection, () => { | ||||
|    localConnection.value = JSON.parse(JSON.stringify(props.connection)); | ||||
| }); | ||||
|  | ||||
| const startConnection = async () => { | ||||
|    await saveConnection(); | ||||
|    isConnecting.value = true; | ||||
|  | ||||
|    if (localConnection.value.ask) | ||||
|       isAsking.value = true; | ||||
|    else { | ||||
|       await connectWorkspace(localConnection.value); | ||||
|       isConnecting.value = false; | ||||
|    } | ||||
| }; | ||||
|  | ||||
| const startTest = async () => { | ||||
|    isTesting.value = true; | ||||
|  | ||||
|    if (localConnection.value.ask) | ||||
|       isAsking.value = true; | ||||
|    else { | ||||
|       try { | ||||
|          const res = await Connection.makeTest(localConnection.value); | ||||
|          if (res.status === 'error') | ||||
|             addNotification({ status: 'error', message: res.response.message || res.response.toString() }); | ||||
|          else | ||||
|             addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') }); | ||||
|       } | ||||
|       catch (err) { | ||||
|          addNotification({ status: 'error', message: err.stack }); | ||||
|       } | ||||
|  | ||||
|       isTesting.value = false; | ||||
|    } | ||||
| }; | ||||
|  | ||||
| const continueTest = async (credentials: {user: string; password: string }) => { // if "Ask for credentials" is true | ||||
|    isAsking.value = false; | ||||
|    const params = Object.assign({}, localConnection.value, credentials); | ||||
|    try { | ||||
|       if (isConnecting.value) { | ||||
|          const params = Object.assign({}, props.connection, credentials); | ||||
|          await connectWorkspace(params); | ||||
|          isConnecting.value = false; | ||||
|       } | ||||
|       else { | ||||
|          const res = await Connection.makeTest(params); | ||||
|          if (res.status === 'error') | ||||
|             addNotification({ status: 'error', message: res.response.message || res.response.toString() }); | ||||
|          else | ||||
|             addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') }); | ||||
|       } | ||||
|    } | ||||
|    catch (err) { | ||||
|       addNotification({ status: 'error', message: err.stack }); | ||||
|    } | ||||
|  | ||||
|    isTesting.value = false; | ||||
| }; | ||||
|  | ||||
| const saveConnection = () => { | ||||
|    return editConnection(localConnection.value); | ||||
| }; | ||||
|  | ||||
| const closeAsking = () => { | ||||
|    isTesting.value = false; | ||||
|    isAsking.value = false; | ||||
|    isConnecting.value = false; | ||||
| }; | ||||
|  | ||||
| const selectTab = (tab: string) => { | ||||
|    selectedTab.value = tab; | ||||
| }; | ||||
|  | ||||
| const toggleSsl = () => { | ||||
|    localConnection.value.ssl = !localConnection.value.ssl; | ||||
| }; | ||||
|  | ||||
| const toggleSsh = () => { | ||||
|    localConnection.value.ssh = !localConnection.value.ssh; | ||||
| }; | ||||
|  | ||||
| const pathSelection = (event: Event & {target: {files: {path: string}[]}}, name: keyof ConnectionParams) => { | ||||
|    const { files } = event.target; | ||||
|    if (!files.length) return; | ||||
|  | ||||
|    (localConnection.value as unknown as {[key: string]: string})[name] = files[0].path; | ||||
| }; | ||||
|  | ||||
| const pathClear = (name: keyof ConnectionParams) => { | ||||
|    (localConnection.value as unknown as {[key: string]: string})[name] = ''; | ||||
| }; | ||||
|  | ||||
| localConnection.value = JSON.parse(JSON.stringify(props.connection)); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
| @@ -1,61 +1,48 @@ | ||||
| <template> | ||||
|    <div class="column col-12 empty"> | ||||
|       <div class="empty-icon"> | ||||
|          <img | ||||
|             v-if="applicationTheme === 'dark'" | ||||
|             src="../images/logo-dark.svg" | ||||
|             width="200" | ||||
|          > | ||||
|          <img | ||||
|             v-if="applicationTheme === 'light'" | ||||
|             src="../images/logo-light.svg" | ||||
|             width="200" | ||||
|          > | ||||
|          <img :src="logos[applicationTheme]" width="200"> | ||||
|       </div> | ||||
|       <p class="h6 empty-subtitle"> | ||||
|          {{ $t('message.noOpenTabs') }} | ||||
|          {{ t('message.noOpenTabs') }} | ||||
|       </p> | ||||
|       <div class="empty-action"> | ||||
|          <button class="btn btn-gray d-flex" @click="$emit('new-tab')"> | ||||
|          <button class="btn btn-gray d-flex" @click="emit('new-tab')"> | ||||
|             <i class="mdi mdi-24px mdi-tab-plus mr-2" /> | ||||
|             {{ $t('message.openNewTab') }} | ||||
|             {{ t('message.openNewTab') }} | ||||
|          </button> | ||||
|       </div> | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| <script setup lang="ts"> | ||||
| import { computed } from 'vue'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
| import { useSettingsStore } from '@/stores/settings'; | ||||
| import { useWorkspacesStore } from '@/stores/workspaces'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
| export default { | ||||
|    name: 'WorkspaceEmptyState', | ||||
|    emits: ['new-tab'], | ||||
|    setup () { | ||||
|       const settingsStore = useSettingsStore(); | ||||
|       const workspacesStore = useWorkspacesStore(); | ||||
| import { useI18n } from 'vue-i18n'; | ||||
|  | ||||
|       const { applicationTheme } = storeToRefs(settingsStore); | ||||
|       const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); | ||||
| const { t } = useI18n(); | ||||
|  | ||||
|       const { getWorkspace, changeBreadcrumbs } = workspacesStore; | ||||
| const emit = defineEmits(['new-tab']); | ||||
|  | ||||
|       return { | ||||
|          applicationTheme, | ||||
|          selectedWorkspace, | ||||
|          getWorkspace, | ||||
|          changeBreadcrumbs | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       workspace () { | ||||
|          return this.getWorkspace(this.selectedWorkspace); | ||||
|       } | ||||
|    }, | ||||
|    created () { | ||||
|       this.changeBreadcrumbs({ schema: this.workspace.breadcrumbs.schema }); | ||||
|    } | ||||
| const logos = { | ||||
|    light: require('../images/logo-light.svg') as string, | ||||
|    dark: require('../images/logo-dark.svg') as string | ||||
| }; | ||||
|  | ||||
| const settingsStore = useSettingsStore(); | ||||
| const workspacesStore = useWorkspacesStore(); | ||||
|  | ||||
| const { applicationTheme } = storeToRefs(settingsStore); | ||||
| const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); | ||||
|  | ||||
| const { getWorkspace, changeBreadcrumbs } = workspacesStore; | ||||
|  | ||||
| const workspace = computed(() => { | ||||
|    return getWorkspace(selectedWorkspace.value); | ||||
| }); | ||||
|  | ||||
| changeBreadcrumbs({ schema: workspace.value.breadcrumbs.schema }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|   | ||||
| @@ -48,7 +48,7 @@ | ||||
|                /> | ||||
|             </div> | ||||
|          </div> | ||||
|          <div class="workspace-explorebar-body" @click="$refs.explorebar.focus()"> | ||||
|          <div class="workspace-explorebar-body" @click="explorebar.focus()"> | ||||
|             <WorkspaceExploreBarSchema | ||||
|                v-for="db of workspace.structure" | ||||
|                :key="db.name" | ||||
| @@ -115,7 +115,8 @@ | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| <script setup lang="ts"> | ||||
| import { Component, computed, onMounted, Ref, ref, watch } from 'vue'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
|  | ||||
| import { useConnectionsStore } from '@/stores/connections'; | ||||
| @@ -125,428 +126,291 @@ import { useWorkspacesStore } from '@/stores/workspaces'; | ||||
|  | ||||
| import Tables from '@/ipc-api/Tables'; | ||||
| import Views from '@/ipc-api/Views'; | ||||
| import Functions from '@/ipc-api/Functions'; | ||||
| import Schedulers from '@/ipc-api/Schedulers'; | ||||
|  | ||||
| import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema'; | ||||
| import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext'; | ||||
| import TableContext from '@/components/WorkspaceExploreBarTableContext'; | ||||
| import MiscContext from '@/components/WorkspaceExploreBarMiscContext'; | ||||
| import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext'; | ||||
| import ModalNewSchema from '@/components/ModalNewSchema'; | ||||
| import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema.vue'; | ||||
| import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext.vue'; | ||||
| import TableContext from '@/components/WorkspaceExploreBarTableContext.vue'; | ||||
| import MiscContext from '@/components/WorkspaceExploreBarMiscContext.vue'; | ||||
| import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext.vue'; | ||||
| import ModalNewSchema from '@/components/ModalNewSchema.vue'; | ||||
|  | ||||
| export default { | ||||
|    name: 'WorkspaceExploreBar', | ||||
|    components: { | ||||
|       WorkspaceExploreBarSchema, | ||||
|       DatabaseContext, | ||||
|       TableContext, | ||||
|       MiscContext, | ||||
|       MiscFolderContext, | ||||
|       ModalNewSchema | ||||
|    }, | ||||
|    props: { | ||||
|       connection: Object, | ||||
|       isSelected: Boolean | ||||
|    }, | ||||
|    setup () { | ||||
|       const { getConnectionName } = useConnectionsStore(); | ||||
|       const { addNotification } = useNotificationsStore(); | ||||
|       const settingsStore = useSettingsStore(); | ||||
|       const workspacesStore = useWorkspacesStore(); | ||||
| const props = defineProps({ | ||||
|    connection: Object, | ||||
|    isSelected: Boolean | ||||
| }); | ||||
|  | ||||
|       const { explorebarSize } = storeToRefs(settingsStore); | ||||
| const { getConnectionName } = useConnectionsStore(); | ||||
| const { addNotification } = useNotificationsStore(); | ||||
| const settingsStore = useSettingsStore(); | ||||
| const workspacesStore = useWorkspacesStore(); | ||||
|  | ||||
|       const { changeExplorebarSize } = settingsStore; | ||||
|       const { | ||||
|          getWorkspace, | ||||
|          removeConnected: disconnectWorkspace, | ||||
|          refreshStructure, | ||||
|          changeBreadcrumbs, | ||||
|          selectTab, | ||||
|          newTab, | ||||
|          removeTabs, | ||||
|          setSearchTerm, | ||||
|          addLoadingElement, | ||||
|          removeLoadingElement | ||||
|       } = workspacesStore; | ||||
| const { explorebarSize } = storeToRefs(settingsStore); | ||||
|  | ||||
|       return { | ||||
|          getConnectionName, | ||||
|          addNotification, | ||||
|          explorebarSize, | ||||
|          changeExplorebarSize, | ||||
|          getWorkspace, | ||||
|          disconnectWorkspace, | ||||
|          refreshStructure, | ||||
|          changeBreadcrumbs, | ||||
|          selectTab, | ||||
|          newTab, | ||||
|          removeTabs, | ||||
|          setSearchTerm, | ||||
|          addLoadingElement, | ||||
|          removeLoadingElement | ||||
|       }; | ||||
|    }, | ||||
|    data () { | ||||
|       return { | ||||
|          isRefreshing: false, | ||||
| const { changeExplorebarSize } = settingsStore; | ||||
| const { | ||||
|    getWorkspace, | ||||
|    removeConnected: disconnectWorkspace, | ||||
|    refreshStructure, | ||||
|    newTab, | ||||
|    removeTabs, | ||||
|    setSearchTerm, | ||||
|    addLoadingElement, | ||||
|    removeLoadingElement | ||||
| } = workspacesStore; | ||||
|  | ||||
|          isNewDBModal: false, | ||||
|          isNewViewModal: false, | ||||
|          isNewTriggerModal: false, | ||||
|          isNewRoutineModal: false, | ||||
|          isNewFunctionModal: false, | ||||
|          isNewTriggerFunctionModal: false, | ||||
|          isNewSchedulerModal: false, | ||||
| const searchInput: Ref<HTMLInputElement> = ref(null); | ||||
| const explorebar: Ref<HTMLInputElement> = ref(null); | ||||
| const resizer: Ref<HTMLInputElement> = ref(null); | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| const schema: Ref<Component & { selectSchema: (name: string) => void; $refs: any }[]> = ref(null); | ||||
| const isRefreshing = ref(false); | ||||
| const isNewDBModal = ref(false); | ||||
| const localWidth = ref(null); | ||||
| const explorebarWidthInterval = ref(null); | ||||
| const searchTermInterval = ref(null); | ||||
| const isDatabaseContext = ref(false); | ||||
| const isTableContext = ref(false); | ||||
| const isMiscContext = ref(false); | ||||
| const isMiscFolderContext = ref(false); | ||||
| const databaseContextEvent = ref(null); | ||||
| const tableContextEvent = ref(null); | ||||
| const miscContextEvent = ref(null); | ||||
| const selectedSchema = ref(''); | ||||
| const selectedTable = ref(null); | ||||
| const selectedMisc = ref(null); | ||||
| const searchTerm = ref(''); | ||||
|  | ||||
|          localWidth: null, | ||||
|          explorebarWidthInterval: null, | ||||
|          searchTermInterval: null, | ||||
|          isDatabaseContext: false, | ||||
|          isTableContext: false, | ||||
|          isMiscContext: false, | ||||
|          isMiscFolderContext: false, | ||||
| const workspace = computed(() => { | ||||
|    return getWorkspace(props.connection.uid); | ||||
| }); | ||||
|  | ||||
|          databaseContextEvent: null, | ||||
|          tableContextEvent: null, | ||||
|          miscContextEvent: null, | ||||
| const connectionName = computed(() => { | ||||
|    return getConnectionName(props.connection.uid); | ||||
| }); | ||||
|  | ||||
|          selectedSchema: '', | ||||
|          selectedTable: null, | ||||
|          selectedMisc: null, | ||||
|          searchTerm: '' | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       workspace () { | ||||
|          return this.getWorkspace(this.connection.uid); | ||||
|       }, | ||||
|       connectionName () { | ||||
|          return this.getConnectionName(this.connection.uid); | ||||
|       }, | ||||
|       customizations () { | ||||
|          return this.workspace.customizations; | ||||
|       } | ||||
|    }, | ||||
|    watch: { | ||||
|       localWidth (val) { | ||||
|          clearTimeout(this.explorebarWidthInterval); | ||||
| const customizations = computed(() => { | ||||
|    return workspace.value.customizations; | ||||
| }); | ||||
|  | ||||
|          this.explorebarWidthInterval = setTimeout(() => { | ||||
|             this.changeExplorebarSize(val); | ||||
|          }, 500); | ||||
|       }, | ||||
|       isSelected (val) { | ||||
|          if (val) this.localWidth = this.explorebarSize; | ||||
|       }, | ||||
|       searchTerm () { | ||||
|          clearTimeout(this.searchTermInterval); | ||||
| watch(localWidth, (val: number) => { | ||||
|    clearTimeout(explorebarWidthInterval.value); | ||||
|  | ||||
|          this.searchTermInterval = setTimeout(() => { | ||||
|             this.setSearchTerm(this.searchTerm); | ||||
|          }, 200); | ||||
|       } | ||||
|    }, | ||||
|    created () { | ||||
|       this.localWidth = this.explorebarSize; | ||||
|    }, | ||||
|    mounted () { | ||||
|       const resizer = this.$refs.resizer; | ||||
|    explorebarWidthInterval.value = setTimeout(() => { | ||||
|       changeExplorebarSize(val); | ||||
|    }, 500); | ||||
| }); | ||||
|  | ||||
|       resizer.addEventListener('mousedown', e => { | ||||
|          e.preventDefault(); | ||||
| watch(() => props.isSelected, (val: boolean) => { | ||||
|    if (val) localWidth.value = explorebarSize.value; | ||||
| }); | ||||
|  | ||||
|          window.addEventListener('mousemove', this.resize); | ||||
|          window.addEventListener('mouseup', this.stopResize); | ||||
|       }); | ||||
| watch(searchTerm, () => { | ||||
|    clearTimeout(searchTermInterval.value); | ||||
|  | ||||
|       if (this.workspace.structure.length === 1) { // Auto-open if juust one schema | ||||
|          this.$refs.schema[0].selectSchema(this.workspace.structure[0].name); | ||||
|          this.$refs.schema[0].$refs.schemaAccordion.open = true; | ||||
|       } | ||||
|    }, | ||||
|    methods: { | ||||
|       async refresh () { | ||||
|          if (!this.isRefreshing) { | ||||
|             this.isRefreshing = true; | ||||
|             await this.refreshStructure(this.connection.uid); | ||||
|             this.isRefreshing = false; | ||||
|          } | ||||
|       }, | ||||
|       explorebarSearch (e) { | ||||
|          if (e.code === 'Backspace') { | ||||
|             e.preventDefault(); | ||||
|             if (this.searchTerm.length) | ||||
|                this.searchTerm = this.searchTerm.slice(0, -1); | ||||
|             else | ||||
|                return; | ||||
|          } | ||||
|          else if (e.key.length > 1)// Prevent non-alphanumerics | ||||
|             return; | ||||
|    searchTermInterval.value = setTimeout(() => { | ||||
|       setSearchTerm(searchTerm.value); | ||||
|    }, 200); | ||||
| }); | ||||
|  | ||||
|          this.$refs.searchInput.focus(); | ||||
|       }, | ||||
|       resize (e) { | ||||
|          const el = this.$refs.explorebar; | ||||
|          let explorebarWidth = e.pageX - el.getBoundingClientRect().left; | ||||
|          if (explorebarWidth > 500) explorebarWidth = 500; | ||||
|          if (explorebarWidth < 150) explorebarWidth = 150; | ||||
|          this.localWidth = explorebarWidth; | ||||
|       }, | ||||
|       stopResize () { | ||||
|          window.removeEventListener('mousemove', this.resize); | ||||
|       }, | ||||
|       showNewDBModal () { | ||||
|          this.isNewDBModal = true; | ||||
|       }, | ||||
|       hideNewDBModal () { | ||||
|          this.isNewDBModal = false; | ||||
|       }, | ||||
|       openCreateElementTab (element) { | ||||
|          this.closeDatabaseContext(); | ||||
|          this.closeMiscFolderContext(); | ||||
| localWidth.value = explorebarSize.value; | ||||
|  | ||||
|          this.newTab({ | ||||
|             uid: this.workspace.uid, | ||||
|             schema: this.selectedSchema, | ||||
|             elementName: '', | ||||
|             elementType: element, | ||||
|             type: `new-${element}` | ||||
|          }); | ||||
|       }, | ||||
|       openSchemaContext (payload) { | ||||
|          this.selectedSchema = payload.schema; | ||||
|          this.databaseContextEvent = payload.event; | ||||
|          this.isDatabaseContext = true; | ||||
|       }, | ||||
|       closeDatabaseContext () { | ||||
|          this.isDatabaseContext = false; | ||||
|       }, | ||||
|       openTableContext (payload) { | ||||
|          this.selectedTable = payload.table; | ||||
|          this.selectedSchema = payload.schema; | ||||
|          this.tableContextEvent = payload.event; | ||||
|          this.isTableContext = true; | ||||
|       }, | ||||
|       closeTableContext () { | ||||
|          this.isTableContext = false; | ||||
|       }, | ||||
|       openMiscContext (payload) { | ||||
|          this.selectedMisc = payload.misc; | ||||
|          this.selectedSchema = payload.schema; | ||||
|          this.miscContextEvent = payload.event; | ||||
|          this.isMiscContext = true; | ||||
|       }, | ||||
|       openMiscFolderContext (payload) { | ||||
|          this.selectedMisc = payload.type; | ||||
|          this.selectedSchema = payload.schema; | ||||
|          this.miscContextEvent = payload.event; | ||||
|          this.isMiscFolderContext = true; | ||||
|       }, | ||||
|       closeMiscContext () { | ||||
|          this.isMiscContext = false; | ||||
|       }, | ||||
|       closeMiscFolderContext () { | ||||
|          this.isMiscFolderContext = false; | ||||
|       }, | ||||
|       showCreateTriggerModal () { | ||||
|          this.closeDatabaseContext(); | ||||
|          this.closeMiscFolderContext(); | ||||
|          this.isNewTriggerModal = true; | ||||
|       }, | ||||
|       hideCreateTriggerModal () { | ||||
|          this.isNewTriggerModal = false; | ||||
|       }, | ||||
|       showCreateRoutineModal () { | ||||
|          this.closeDatabaseContext(); | ||||
|          this.closeMiscFolderContext(); | ||||
|          this.isNewRoutineModal = true; | ||||
|       }, | ||||
|       hideCreateRoutineModal () { | ||||
|          this.isNewRoutineModal = false; | ||||
|       }, | ||||
|       showCreateFunctionModal () { | ||||
|          this.closeDatabaseContext(); | ||||
|          this.closeMiscFolderContext(); | ||||
|          this.isNewFunctionModal = true; | ||||
|       }, | ||||
|       hideCreateFunctionModal () { | ||||
|          this.isNewFunctionModal = false; | ||||
|       }, | ||||
|       showCreateTriggerFunctionModal () { | ||||
|          this.closeDatabaseContext(); | ||||
|          this.closeMiscFolderContext(); | ||||
|          this.isNewTriggerFunctionModal = true; | ||||
|       }, | ||||
|       hideCreateTriggerFunctionModal () { | ||||
|          this.isNewTriggerFunctionModal = false; | ||||
|       }, | ||||
|       showCreateSchedulerModal () { | ||||
|          this.closeDatabaseContext(); | ||||
|          this.closeMiscFolderContext(); | ||||
|          this.isNewSchedulerModal = true; | ||||
|       }, | ||||
|       hideCreateSchedulerModal () { | ||||
|          this.isNewSchedulerModal = false; | ||||
|       }, | ||||
|       async deleteTable (payload) { | ||||
|          this.closeTableContext(); | ||||
| onMounted(() => { | ||||
|    resizer.value.addEventListener('mousedown', (e: MouseEvent) => { | ||||
|       e.preventDefault(); | ||||
|  | ||||
|          this.addLoadingElement({ | ||||
|             name: payload.table.name, | ||||
|             schema: payload.schema, | ||||
|             type: 'table' | ||||
|          }); | ||||
|       window.addEventListener('mousemove', resize); | ||||
|       window.addEventListener('mouseup', stopResize); | ||||
|    }); | ||||
|  | ||||
|          try { | ||||
|             let res; | ||||
|    if (workspace.value.structure.length === 1) { // Auto-open if juust one schema | ||||
|       schema.value[0].selectSchema(workspace.value.structure[0].name); | ||||
|       schema.value[0].$refs.schemaAccordion.open = true; | ||||
|    } | ||||
| }); | ||||
|  | ||||
|             if (payload.table.type === 'table') { | ||||
|                res = await Tables.dropTable({ | ||||
|                   uid: this.connection.uid, | ||||
|                   table: payload.table.name, | ||||
|                   schema: payload.schema | ||||
|                }); | ||||
|             } | ||||
|             else if (payload.table.type === 'view') { | ||||
|                res = await Views.dropView({ | ||||
|                   uid: this.connection.uid, | ||||
|                   view: payload.table.name, | ||||
|                   schema: payload.schema | ||||
|                }); | ||||
|             } | ||||
|  | ||||
|             const { status, response } = res; | ||||
|  | ||||
|             if (status === 'success') { | ||||
|                this.refresh(); | ||||
|  | ||||
|                this.removeTabs({ | ||||
|                   uid: this.connection.uid, | ||||
|                   elementName: payload.table.name, | ||||
|                   elementType: payload.table.type, | ||||
|                   schema: payload.schema | ||||
|                }); | ||||
|             } | ||||
|             else | ||||
|                this.addNotification({ status: 'error', message: response }); | ||||
|          } | ||||
|          catch (err) { | ||||
|             this.addNotification({ status: 'error', message: err.stack }); | ||||
|          } | ||||
|  | ||||
|          this.removeLoadingElement({ | ||||
|             name: payload.table.name, | ||||
|             schema: payload.schema, | ||||
|             type: 'table' | ||||
|          }); | ||||
|       }, | ||||
|       async duplicateTable (payload) { | ||||
|          this.closeTableContext(); | ||||
|  | ||||
|          this.addLoadingElement({ | ||||
|             name: payload.table.name, | ||||
|             schema: payload.schema, | ||||
|             type: 'table' | ||||
|          }); | ||||
|  | ||||
|          try { | ||||
|             const { status, response } = await Tables.duplicateTable({ | ||||
|                uid: this.connection.uid, | ||||
|                table: payload.table.name, | ||||
|                schema: payload.schema | ||||
|             }); | ||||
|  | ||||
|             if (status === 'success') | ||||
|                this.refresh(); | ||||
|             else | ||||
|                this.addNotification({ status: 'error', message: response }); | ||||
|          } | ||||
|          catch (err) { | ||||
|             this.addNotification({ status: 'error', message: err.stack }); | ||||
|          } | ||||
|  | ||||
|          this.removeLoadingElement({ | ||||
|             name: payload.table.name, | ||||
|             schema: payload.schema, | ||||
|             type: 'table' | ||||
|          }); | ||||
|       }, | ||||
|       async openCreateFunctionEditor (payload) { | ||||
|          const params = { | ||||
|             uid: this.connection.uid, | ||||
|             schema: this.selectedSchema, | ||||
|             ...payload | ||||
|          }; | ||||
|  | ||||
|          const { status, response } = await Functions.createFunction(params); | ||||
|  | ||||
|          if (status === 'success') { | ||||
|             await this.refresh(); | ||||
|             this.changeBreadcrumbs({ schema: this.selectedSchema, function: payload.name }); | ||||
|  | ||||
|             this.newTab({ | ||||
|                uid: this.workspace.uid, | ||||
|                schema: this.selectedSchema, | ||||
|                elementName: payload.name, | ||||
|                elementType: 'function', | ||||
|                type: 'function-props' | ||||
|             }); | ||||
|          } | ||||
|          else | ||||
|             this.addNotification({ status: 'error', message: response }); | ||||
|       }, | ||||
|       async openCreateTriggerFunctionEditor (payload) { | ||||
|          const params = { | ||||
|             uid: this.connection.uid, | ||||
|             schema: this.selectedSchema, | ||||
|             ...payload | ||||
|          }; | ||||
|  | ||||
|          const { status, response } = await Functions.createTriggerFunction(params); | ||||
|  | ||||
|          if (status === 'success') { | ||||
|             await this.refresh(); | ||||
|             this.changeBreadcrumbs({ schema: this.selectedSchema, triggerFunction: payload.name }); | ||||
|  | ||||
|             this.newTab({ | ||||
|                uid: this.workspace.uid, | ||||
|                schema: this.selectedSchema, | ||||
|                elementName: payload.name, | ||||
|                elementType: 'triggerFunction', | ||||
|                type: 'trigger-function-props' | ||||
|             }); | ||||
|          } | ||||
|          else | ||||
|             this.addNotification({ status: 'error', message: response }); | ||||
|       }, | ||||
|       async openCreateSchedulerEditor (payload) { | ||||
|          const params = { | ||||
|             uid: this.connection.uid, | ||||
|             schema: this.selectedSchema, | ||||
|             ...payload | ||||
|          }; | ||||
|  | ||||
|          const { status, response } = await Schedulers.createScheduler(params); | ||||
|  | ||||
|          if (status === 'success') { | ||||
|             await this.refresh(); | ||||
|             this.changeBreadcrumbs({ schema: this.selectedSchema, scheduler: payload.name }); | ||||
|  | ||||
|             this.newTab({ | ||||
|                uid: this.workspace.uid, | ||||
|                schema: this.selectedSchema, | ||||
|                elementName: payload.name, | ||||
|                elementType: 'scheduler', | ||||
|                type: 'scheduler-props' | ||||
|             }); | ||||
|          } | ||||
|          else | ||||
|             this.addNotification({ status: 'error', message: response }); | ||||
|       } | ||||
| const refresh = async () => { | ||||
|    if (!isRefreshing.value) { | ||||
|       isRefreshing.value = true; | ||||
|       await refreshStructure(props.connection.uid); | ||||
|       isRefreshing.value = false; | ||||
|    } | ||||
| }; | ||||
|  | ||||
| const explorebarSearch = (e: KeyboardEvent) => { | ||||
|    if (e.code === 'Backspace') { | ||||
|       e.preventDefault(); | ||||
|       if (searchTerm.value.length) | ||||
|          searchTerm.value = searchTerm.value.slice(0, -1); | ||||
|       else | ||||
|          return; | ||||
|    } | ||||
|    else if (e.key.length > 1)// Prevent non-alphanumerics | ||||
|       return; | ||||
|  | ||||
|    searchInput.value.focus(); | ||||
| }; | ||||
|  | ||||
| const resize = (e: MouseEvent) => { | ||||
|    const el = explorebar.value; | ||||
|    let explorebarWidth = e.pageX - el.getBoundingClientRect().left; | ||||
|    if (explorebarWidth > 500) explorebarWidth = 500; | ||||
|    if (explorebarWidth < 150) explorebarWidth = 150; | ||||
|    localWidth.value = explorebarWidth; | ||||
| }; | ||||
|  | ||||
| const stopResize = () => { | ||||
|    window.removeEventListener('mousemove', resize); | ||||
| }; | ||||
|  | ||||
| const showNewDBModal = () => { | ||||
|    isNewDBModal.value = true; | ||||
| }; | ||||
|  | ||||
| const hideNewDBModal = () => { | ||||
|    isNewDBModal.value = false; | ||||
| }; | ||||
|  | ||||
| const openCreateElementTab = (element: string) => { | ||||
|    closeDatabaseContext(); | ||||
|    closeMiscFolderContext(); | ||||
|  | ||||
|    newTab({ | ||||
|       uid: workspace.value.uid, | ||||
|       schema: selectedSchema.value, | ||||
|       elementName: '', | ||||
|       elementType: element, | ||||
|       type: `new-${element}` | ||||
|    }); | ||||
| }; | ||||
|  | ||||
| const openSchemaContext = (payload: { schema: string; event: PointerEvent }) => { | ||||
|    selectedSchema.value = payload.schema; | ||||
|    databaseContextEvent.value = payload.event; | ||||
|    isDatabaseContext.value = true; | ||||
| }; | ||||
|  | ||||
| const closeDatabaseContext = () => { | ||||
|    isDatabaseContext.value = false; | ||||
| }; | ||||
|  | ||||
| const openTableContext = (payload: { schema: string; table: string; event: PointerEvent }) => { | ||||
|    selectedTable.value = payload.table; | ||||
|    selectedSchema.value = payload.schema; | ||||
|    tableContextEvent.value = payload.event; | ||||
|    isTableContext.value = true; | ||||
| }; | ||||
|  | ||||
| const closeTableContext = () => { | ||||
|    isTableContext.value = false; | ||||
| }; | ||||
|  | ||||
| const openMiscContext = (payload: { schema: string; misc: string; event: PointerEvent }) => { | ||||
|    selectedMisc.value = payload.misc; | ||||
|    selectedSchema.value = payload.schema; | ||||
|    miscContextEvent.value = payload.event; | ||||
|    isMiscContext.value = true; | ||||
| }; | ||||
|  | ||||
| const openMiscFolderContext = (payload: { schema: string; type: string; event: PointerEvent }) => { | ||||
|    selectedMisc.value = payload.type; | ||||
|    selectedSchema.value = payload.schema; | ||||
|    miscContextEvent.value = payload.event; | ||||
|    isMiscFolderContext.value = true; | ||||
| }; | ||||
|  | ||||
| const closeMiscContext = () => { | ||||
|    isMiscContext.value = false; | ||||
| }; | ||||
|  | ||||
| const closeMiscFolderContext = () => { | ||||
|    isMiscFolderContext.value = false; | ||||
| }; | ||||
|  | ||||
| const deleteTable = async (payload: { schema: string; table: { name: string; type: string }; event: PointerEvent }) => { | ||||
|    closeTableContext(); | ||||
|  | ||||
|    addLoadingElement({ | ||||
|       name: payload.table.name, | ||||
|       schema: payload.schema, | ||||
|       type: 'table' | ||||
|    }); | ||||
|  | ||||
|    try { | ||||
|       let res; | ||||
|  | ||||
|       if (payload.table.type === 'table') { | ||||
|          res = await Tables.dropTable({ | ||||
|             uid: props.connection.uid, | ||||
|             table: payload.table.name, | ||||
|             schema: payload.schema | ||||
|          }); | ||||
|       } | ||||
|       else if (payload.table.type === 'view') { | ||||
|          res = await Views.dropView({ | ||||
|             uid: props.connection.uid, | ||||
|             view: payload.table.name, | ||||
|             schema: payload.schema | ||||
|          }); | ||||
|       } | ||||
|  | ||||
|       const { status, response } = res; | ||||
|  | ||||
|       if (status === 'success') { | ||||
|          refresh(); | ||||
|  | ||||
|          removeTabs({ | ||||
|             uid: props.connection.uid as string, | ||||
|             elementName: payload.table.name as string, | ||||
|             elementType: payload.table.type, | ||||
|             schema: payload.schema as string | ||||
|          }); | ||||
|       } | ||||
|       else | ||||
|          addNotification({ status: 'error', message: response }); | ||||
|    } | ||||
|    catch (err) { | ||||
|       addNotification({ status: 'error', message: err.stack }); | ||||
|    } | ||||
|  | ||||
|    removeLoadingElement({ | ||||
|       name: payload.table.name, | ||||
|       schema: payload.schema, | ||||
|       type: 'table' | ||||
|    }); | ||||
| }; | ||||
|  | ||||
| const duplicateTable = async (payload: { schema: string; table: { name: string }; event: PointerEvent }) => { | ||||
|    closeTableContext(); | ||||
|  | ||||
|    addLoadingElement({ | ||||
|       name: payload.table.name, | ||||
|       schema: payload.schema, | ||||
|       type: 'table' | ||||
|    }); | ||||
|  | ||||
|    try { | ||||
|       const { status, response } = await Tables.duplicateTable({ | ||||
|          uid: props.connection.uid, | ||||
|          table: payload.table.name, | ||||
|          schema: payload.schema | ||||
|       }); | ||||
|  | ||||
|       if (status === 'success') | ||||
|          refresh(); | ||||
|       else | ||||
|          addNotification({ status: 'error', message: response }); | ||||
|    } | ||||
|    catch (err) { | ||||
|       addNotification({ status: 'error', message: err.stack }); | ||||
|    } | ||||
|  | ||||
|    removeLoadingElement({ | ||||
|       name: payload.table.name, | ||||
|       schema: payload.schema, | ||||
|       type: 'table' | ||||
|    }); | ||||
| }; | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
|   | ||||
| @@ -248,6 +248,8 @@ import { useWorkspacesStore } from '@/stores/workspaces'; | ||||
| import { formatBytes } from 'common/libs/formatBytes'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
|  | ||||
| // TODO: expose selectSchema & schemaAccordion | ||||
|  | ||||
| export default { | ||||
|    name: 'WorkspaceExploreBarSchema', | ||||
|    props: { | ||||
|   | ||||
| @@ -132,7 +132,7 @@ | ||||
|             <input | ||||
|                v-model="localRow.unsigned" | ||||
|                type="checkbox" | ||||
|                :disabled="!fieldType.unsigned" | ||||
|                :disabled="!fieldType?.unsigned" | ||||
|             > | ||||
|             <i class="form-icon" /> | ||||
|          </label> | ||||
|   | ||||
| @@ -151,7 +151,7 @@ | ||||
|  | ||||
|                   <BaseSelect | ||||
|                      v-model="selectedSchema" | ||||
|                      :options="[{value: 'null', label: $t('message.noSchema')}, ...databaseSchemas.map(el => ({label: el, value: el}))]" | ||||
|                      :options="[{value: null, label: $t('message.noSchema')}, ...databaseSchemas.map(el => ({label: el, value: el}))]" | ||||
|                      class="form-select select-sm text-bold" | ||||
|                   /> | ||||
|                </div> | ||||
|   | ||||
| @@ -29,7 +29,7 @@ createApp(App) | ||||
|    .mount('#app'); | ||||
|  | ||||
| const { locale } = useSettingsStore(); | ||||
| i18n.global.locale = locale as string;// TODO: temp | ||||
| i18n.global.locale = locale; | ||||
|  | ||||
| // IPC exceptions | ||||
| ipcRenderer.on('unhandled-exception', (event, error) => { | ||||
|   | ||||
| @@ -34,7 +34,7 @@ export const useApplicationStore = defineStore('application', { | ||||
|       setLoadingStatus (payload: boolean) { | ||||
|          this.isLoading = payload; | ||||
|       }, | ||||
|       setBaseCompleters (payload: boolean) { | ||||
|       setBaseCompleters (payload: Ace.Completer[]) { | ||||
|          this.baseCompleter = payload; | ||||
|       }, | ||||
|       // Modals | ||||
|   | ||||
| @@ -60,7 +60,7 @@ export const useConnectionsStore = defineStore('connections', { | ||||
|          this.selected_conection = {}; | ||||
|          persistentStore.set('connections', this.connections); | ||||
|       }, | ||||
|       updateConnections (connections: ConnectionParams) { | ||||
|       updateConnections (connections: ConnectionParams[]) { | ||||
|          this.connections = connections; | ||||
|          persistentStore.set('connections', this.connections); | ||||
|       } | ||||
|   | ||||
| @@ -7,6 +7,7 @@ const defaultAppTheme = isDarkTheme.matches ? 'dark' : 'light'; | ||||
| const defaultEditorTheme = isDarkTheme.matches ? 'twilight' : 'sqlserver'; | ||||
|  | ||||
| export type EditorFontSize = 'small' | 'medium' | 'large'; | ||||
| export type ApplicationTheme = 'light' | 'dark'; | ||||
|  | ||||
| export const useSettingsStore = defineStore('settings', { | ||||
|    state: () => ({ | ||||
| @@ -17,7 +18,7 @@ export const useSettingsStore = defineStore('settings', { | ||||
|       dataTabLimit: persistentStore.get('data_tab_limit', 1000) as number, | ||||
|       autoComplete: persistentStore.get('auto_complete', true) as boolean, | ||||
|       lineWrap: persistentStore.get('line_wrap', true) as boolean, | ||||
|       applicationTheme: persistentStore.get('application_theme', defaultAppTheme) as string, | ||||
|       applicationTheme: persistentStore.get('application_theme', defaultAppTheme) as ApplicationTheme, | ||||
|       editorTheme: persistentStore.get('editor_theme', defaultEditorTheme) as string, | ||||
|       editorFontSize: persistentStore.get('editor_font_size', 'medium') as EditorFontSize, | ||||
|       restoreTabs: persistentStore.get('restore_tabs', true) as boolean, | ||||
|   | ||||
| @@ -25,16 +25,16 @@ import { Customizations } from 'common/interfaces/customizations'; | ||||
| export interface WorkspaceTab { | ||||
|    uid: string; | ||||
|    tab?: string; | ||||
|    index: number; | ||||
|    selected: boolean; | ||||
|    type: string; | ||||
|    index?: number; | ||||
|    selected?: boolean; | ||||
|    type?: string; | ||||
|    schema?: string; | ||||
|    elementName?: string; | ||||
|    elementNewName?: string; | ||||
|    elementType?: string; | ||||
|    isChanged?: boolean; | ||||
|    content?: string; | ||||
|    autorun: boolean; | ||||
|    autorun?: boolean; | ||||
| } | ||||
|  | ||||
| export interface WorkspaceStructure { | ||||
| @@ -70,7 +70,7 @@ export interface Workspace { | ||||
|    variables: { name: string; value: string }[]; | ||||
|    collations: CollationInfos[]; | ||||
|    users: { host: string; name: string; password: string }[]; | ||||
|    breadcrumbs: Breadcrumb[]; | ||||
|    breadcrumbs: Breadcrumb; | ||||
|    loadingElements: { name: string; schema: string; type: string }[]; | ||||
|    loadedSchemas: Set<string>; | ||||
|    dataTypes?: { [key: string]: TypesGroup[] }; | ||||
| @@ -171,18 +171,18 @@ export const useWorkspacesStore = defineStore('workspaces', { | ||||
|                switch (connection.client) { | ||||
|                   case 'mysql': | ||||
|                   case 'maria': | ||||
|                      dataTypes = require('common/data-types/mysql'); | ||||
|                      indexTypes = require('common/index-types/mysql'); | ||||
|                      dataTypes = require('common/data-types/mysql').default; | ||||
|                      indexTypes = require('common/index-types/mysql').default; | ||||
|                      clientCustomizations = customizations.mysql; | ||||
|                      break; | ||||
|                   case 'pg': | ||||
|                      dataTypes = require('common/data-types/postgresql'); | ||||
|                      indexTypes = require('common/index-types/postgresql'); | ||||
|                      dataTypes = require('common/data-types/postgresql').default; | ||||
|                      indexTypes = require('common/index-types/postgresql').default; | ||||
|                      clientCustomizations = customizations.pg; | ||||
|                      break; | ||||
|                   case 'sqlite': | ||||
|                      dataTypes = require('common/data-types/sqlite'); | ||||
|                      indexTypes = require('common/index-types/sqlite'); | ||||
|                      dataTypes = require('common/data-types/sqlite').default; | ||||
|                      indexTypes = require('common/index-types/sqlite').default; | ||||
|                      clientCustomizations = customizations.sqlite; | ||||
|                      break; | ||||
|                } | ||||
| @@ -392,7 +392,7 @@ export const useWorkspacesStore = defineStore('workspaces', { | ||||
|             variables: [], | ||||
|             collations: [], | ||||
|             users: [], | ||||
|             breadcrumbs: [], | ||||
|             breadcrumbs: {}, | ||||
|             loadingElements: [], | ||||
|             loadedSchemas: new Set() | ||||
|          }; | ||||
| @@ -684,7 +684,7 @@ export const useWorkspacesStore = defineStore('workspaces', { | ||||
|             : workspace | ||||
|          ); | ||||
|       }, | ||||
|       updateTabs ({ uid, tabs }: {uid: string; tabs: string[]}) { | ||||
|       updateTabs ({ uid, tabs }: {uid: string; tabs: WorkspaceTab[]}) { | ||||
|          this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid | ||||
|             ? { ...workspace, tabs } | ||||
|             : workspace | ||||
|   | ||||
		Reference in New Issue
	
	Block a user