1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

refactor: ts and composition api on WorkspaceTabNew* components

This commit is contained in:
2022-06-14 20:02:17 +02:00
parent 33a4663694
commit 89e8d9fcdb
17 changed files with 1321 additions and 1471 deletions

View File

@ -93,6 +93,8 @@ export interface TableInfos {
} }
export interface TableField { export interface TableField {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string; name: string;
key: string; key: string;
type: string; type: string;
@ -120,6 +122,8 @@ export interface TableField {
} }
export interface TableIndex { export interface TableIndex {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string; name: string;
fields: string[]; fields: string[];
type: string; type: string;
@ -249,14 +253,20 @@ export interface FunctionParam {
export interface RoutineInfos { export interface RoutineInfos {
name: string; name: string;
type: string; type?: string;
definer: string; definer: string;
created: string; created?: string;
updated: string; sql?: string;
updated?: string;
comment?: string; comment?: string;
charset?: string; charset?: string;
security?: string; security?: string;
language?: string;
dataAccess?: string;
deterministic?: boolean;
parameters?: FunctionParam[]; parameters?: FunctionParam[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
returns?: any;
} }
export type FunctionInfos = RoutineInfos export type FunctionInfos = RoutineInfos

View File

@ -85,7 +85,7 @@ else {
ipcMain.on('refresh-theme-settings', () => { ipcMain.on('refresh-theme-settings', () => {
const appTheme = persistentStore.get('application_theme'); const appTheme = persistentStore.get('application_theme');
if(isWindows){ if (isWindows) {
mainWindow.setTitleBarOverlay({ mainWindow.setTitleBarOverlay({
color: appTheme === 'dark' ? '#3f3f3f' : '#fff', color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000' symbolColor: appTheme === 'dark' ? '#fff' : '#000'

View File

@ -139,7 +139,7 @@ export default defineComponent({
const hightlightedIndex = ref(0); const hightlightedIndex = ref(0);
const isOpen = ref(false); const isOpen = ref(false);
const isMouseDown = ref(false); const isMouseDown = ref(false);
const internalValue = ref(props.modelValue || props.value); const internalValue = ref(props.modelValue !== false ? props.modelValue : props.value);
const el = ref(null); const el = ref(null);
const searchInput = ref(null); const searchInput = ref(null);
const optionList = ref(null); const optionList = ref(null);
@ -380,7 +380,8 @@ export default defineComponent({
optionList, optionList,
optionRefs, optionRefs,
handleBlurEvent, handleBlurEvent,
handleMouseUpEvent handleMouseUpEvent,
internalValue
}; };
} }
}); });

View File

@ -133,8 +133,8 @@ const mode = computed(() => {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
return 'mysql'; return 'mysql';
case 'mssql': // case 'mssql':
return 'sqlserver'; // return 'sqlserver';
case 'pg': case 'pg':
return 'pgsql'; return 'pgsql';
default: default:

View File

@ -4,7 +4,7 @@
@close-context="closeContext" @close-context="closeContext"
> >
<div <div
v-if="['procedure', 'function'].includes(selectedMisc.type)" v-if="['procedure', 'routine', 'function'].includes(selectedMisc.type)"
class="context-element" class="context-element"
@click="runElementCheck" @click="runElementCheck"
> >
@ -77,7 +77,7 @@ import Triggers from '@/ipc-api/Triggers';
import Routines from '@/ipc-api/Routines'; import Routines from '@/ipc-api/Routines';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import Schedulers from '@/ipc-api/Schedulers'; import Schedulers from '@/ipc-api/Schedulers';
import { EventInfos, FunctionInfos, RoutineInfos, TriggerInfos } from 'common/interfaces/antares'; import { EventInfos, FunctionInfos, IpcResponse, RoutineInfos, TriggerInfos } from 'common/interfaces/antares';
const { t } = useI18n(); const { t } = useI18n();
@ -153,7 +153,7 @@ const closeContext = () => {
const deleteMisc = async () => { const deleteMisc = async () => {
try { try {
let res; let res: IpcResponse;
switch (props.selectedMisc.type) { switch (props.selectedMisc.type) {
case 'trigger': case 'trigger':
@ -163,6 +163,7 @@ const deleteMisc = async () => {
trigger: props.selectedMisc.name trigger: props.selectedMisc.name
}); });
break; break;
case 'routine':
case 'procedure': case 'procedure':
res = await Routines.dropRoutine({ res = await Routines.dropRoutine({
uid: selectedWorkspace.value, uid: selectedWorkspace.value,
@ -187,6 +188,8 @@ const deleteMisc = async () => {
break; break;
} }
console.log(res);
const { status, response } = res; const { status, response } = res;
if (status === 'success') { if (status === 'success') {
@ -209,7 +212,7 @@ const deleteMisc = async () => {
}; };
const runElementCheck = () => { const runElementCheck = () => {
if (props.selectedMisc.type === 'procedure') if (['procedure', 'routine'].includes(props.selectedMisc.type))
runRoutineCheck(); runRoutineCheck();
else if (props.selectedMisc.type === 'function') else if (props.selectedMisc.type === 'function')
runFunctionCheck(); runFunctionCheck();
@ -257,9 +260,9 @@ const runRoutine = (params?: string[]) => {
case 'pg': case 'pg':
sql = `CALL ${localElement.value.name}(${params.join(',')})`; sql = `CALL ${localElement.value.name}(${params.join(',')})`;
break; break;
case 'mssql': // case 'mssql':
sql = `EXEC ${localElement.value.name} ${params.join(',')}`; // sql = `EXEC ${localElement.value.name} ${params.join(',')}`;
break; // break;
default: default:
sql = `CALL \`${localElement.value.name}\`(${params.join(',')})`; sql = `CALL \`${localElement.value.name}\`(${params.join(',')})`;
} }
@ -310,9 +313,9 @@ const runFunction = (params?: string[]) => {
case 'pg': case 'pg':
sql = `SELECT ${localElement.value.name}(${params.join(',')})`; sql = `SELECT ${localElement.value.name}(${params.join(',')})`;
break; break;
case 'mssql': // case 'mssql':
sql = `SELECT ${localElement.value.name} ${params.join(',')}`; // sql = `SELECT ${localElement.value.name} ${params.join(',')}`;
break; // break;
default: default:
sql = `SELECT \`${localElement.value.name}\` (${params.join(',')})`; sql = `SELECT \`${localElement.value.name}\` (${params.join(',')})`;
} }

View File

@ -11,7 +11,7 @@
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('message.createNewTrigger') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('message.createNewTrigger') }}</span>
</div> </div>
<div <div
v-if="props.selectedMisc === 'procedure'" v-if="['procedure', 'routine'].includes(props.selectedMisc)"
class="context-element" class="context-element"
@click="emit('open-create-routine-tab')" @click="emit('open-create-routine-tab')"
> >

View File

@ -72,8 +72,8 @@
<BaseSelect <BaseSelect
v-model="localFunction.definer" v-model="localFunction.definer"
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]" :options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
@ -87,7 +87,7 @@
<BaseSelect <BaseSelect
v-model="localFunction.returns" v-model="localFunction.returns"
class="form-select text-uppercase" class="form-select text-uppercase"
:options="[{ name: 'VOID' }, ...workspace.dataTypes]" :options="[{ name: 'VOID' }, ...(workspace.dataTypes as any)]"
group-label="group" group-label="group"
group-values="types" group-values="types"
option-label="name" option-label="name"
@ -175,213 +175,174 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import Functions from '@/ipc-api/Functions';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal'; import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal.vue';
import Functions from '@/ipc-api/Functions';
import { storeToRefs } from 'pinia';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { FunctionInfos, FunctionParam } from 'common/interfaces/antares';
export default { const props = defineProps({
name: 'WorkspaceTabNewFunction',
components: {
BaseLoader,
QueryEditor,
WorkspaceTabPropsFunctionParamsModal,
BaseSelect
},
props: {
tabUid: String, tabUid: String,
connection: Object, connection: Object,
tab: Object, tab: Object,
isSelected: Boolean, isSelected: Boolean,
schema: String schema: String
}, });
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { const {
getWorkspace, getWorkspace,
refreshStructure, refreshStructure,
changeBreadcrumbs, changeBreadcrumbs,
setUnsavedChanges, setUnsavedChanges,
newTab, newTab,
removeTab, removeTab
renameTabs } = workspacesStore;
} = workspacesStore;
return { const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
addNotification, const firstInput: Ref<HTMLInputElement> = ref(null);
selectedWorkspace, const isLoading = ref(false);
getWorkspace, const isSaving = ref(false);
refreshStructure, const isParamsModal = ref(false);
changeBreadcrumbs, const originalFunction: Ref<FunctionInfos> = ref(null);
setUnsavedChanges, const localFunction = ref(null);
newTab, const editorHeight = ref(300);
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
isParamsModal: false,
originalFunction: {},
localFunction: {},
lastFunction: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
},
isDefinerInUsers () {
return this.originalFunction
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
: true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const workspace = computed(() => {
} return getWorkspace(props.connection.uid);
}, });
watch: {
isSelected (val) {
if (val)
this.changeBreadcrumbs({ schema: this.schema });
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.originalFunction = {
sql: this.customizations.functionSql,
language: this.customizations.languages ? this.customizations.languages[0] : null,
name: '',
definer: '',
comment: '',
security: 'DEFINER',
dataAccess: 'CONTAINS SQL',
deterministic: false,
returns: this.workspace.dataTypes.length ? this.workspace.dataTypes[0].types[0].name : null
};
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction)); const customizations = computed(() => {
return workspace.value.customizations;
});
setTimeout(() => { const isChanged = computed(() => {
this.resizeQueryEditor(); return JSON.stringify(originalFunction.value) !== JSON.stringify(localFunction.value);
}, 50); });
window.addEventListener('keydown', this.onKey); const saveChanges = async () => {
}, if (isSaving.value) return;
mounted () { isSaving.value = true;
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => {
this.$refs.firstInput.focus();
}, 100);
},
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = { const params = {
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
...this.localFunction ...localFunction.value
}; };
try { try {
const { status, response } = await Functions.createFunction(params); const { status, response } = await Functions.createFunction(params);
if (status === 'success') { if (status === 'success') {
await this.refreshStructure(this.connection.uid); await refreshStructure(props.connection.uid);
this.newTab({ newTab({
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
elementName: this.localFunction.name, elementName: localFunction.value.name,
elementType: 'function', elementType: 'function',
type: 'function-props' type: 'function-props'
}); });
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid }); removeTab({ uid: props.connection.uid, tab: props.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, function: this.localFunction.name }); changeBreadcrumbs({ schema: props.schema, function: localFunction.value.name });
} }
else else
this.addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
} }
this.isSaving = false; isSaving.value = false;
}, };
clearChanges () {
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction)); const clearChanges = () => {
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql); localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
}, queryEditor.value.editor.session.setValue(localFunction.value.sql);
resizeQueryEditor () { };
if (this.$refs.queryEditor) {
const resizeQueryEditor = () => {
if (queryEditor.value) {
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight; const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size; editorHeight.value = size;
this.$refs.queryEditor.editor.resize(); queryEditor.value.editor.resize();
} }
}, };
optionsUpdate (options) {
this.localFunction = options; const parametersUpdate = (parameters: FunctionParam[]) => {
}, localFunction.value = { ...localFunction.value, parameters };
parametersUpdate (parameters) { };
this.localFunction = { ...this.localFunction, parameters };
}, const showParamsModal = () => {
showParamsModal () { isParamsModal.value = true;
this.isParamsModal = true; };
},
hideParamsModal () { const hideParamsModal = () => {
this.isParamsModal = false; isParamsModal.value = false;
}, };
showAskParamsModal () {
this.isAskingParameters = true; const onKey = (e: KeyboardEvent) => {
}, if (props.isSelected) {
hideAskParamsModal () {
this.isAskingParameters = false;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation(); e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S if (e.ctrlKey && e.key === 's') { // CTRL + S
if (this.isChanged) if (isChanged.value)
this.saveChanges(); saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalFunction.value = {
sql: customizations.value.functionSql,
language: customizations.value.languages ? customizations.value.languages[0] : null,
name: '',
definer: '',
comment: '',
security: 'DEFINER',
dataAccess: 'CONTAINS SQL',
deterministic: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
returns: workspace.value.dataTypes.length ? (workspace.value.dataTypes[0] as any).types[0].name : null
};
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
</script> </script>

View File

@ -72,8 +72,8 @@
<BaseSelect <BaseSelect
v-model="localRoutine.definer" v-model="localRoutine.definer"
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]" :options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
@ -129,7 +129,6 @@
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label> <label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
:key="`new-${_uid}`"
ref="queryEditor" ref="queryEditor"
v-model="localRoutine.sql" v-model="localRoutine.sql"
:workspace="workspace" :workspace="workspace"
@ -148,209 +147,174 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import Routines from '@/ipc-api/Routines';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal'; import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue';
import Routines from '@/ipc-api/Routines';
import { storeToRefs } from 'pinia';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { FunctionParam } from 'common/interfaces/antares';
export default { const props = defineProps({
name: 'WorkspaceTabNewRoutine',
components: {
QueryEditor,
BaseLoader,
WorkspaceTabPropsRoutineParamsModal,
BaseSelect
},
props: {
tabUid: String, tabUid: String,
connection: Object, connection: Object,
tab: Object, tab: Object,
isSelected: Boolean, isSelected: Boolean,
schema: String schema: String
}, });
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { const {
getWorkspace, getWorkspace,
refreshStructure, refreshStructure,
changeBreadcrumbs, changeBreadcrumbs,
setUnsavedChanges, setUnsavedChanges,
newTab, newTab,
removeTab, removeTab
renameTabs } = workspacesStore;
} = workspacesStore;
return { const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
addNotification, const firstInput: Ref<HTMLInputElement> = ref(null);
selectedWorkspace, const isLoading = ref(false);
getWorkspace, const isSaving = ref(false);
refreshStructure, const isParamsModal = ref(false);
changeBreadcrumbs, const originalRoutine = ref(null);
setUnsavedChanges, const localRoutine = ref(null);
newTab, const editorHeight = ref(300);
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
isParamsModal: false,
originalRoutine: {},
localRoutine: {},
lastRoutine: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
isChanged () {
return JSON.stringify(this.originalRoutine) !== JSON.stringify(this.localRoutine);
},
isDefinerInUsers () {
return this.originalRoutine ? this.workspace.users.some(user => this.originalRoutine.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const workspace = computed(() => {
} return getWorkspace(props.connection.uid);
}, });
watch: {
isSelected (val) {
if (val)
this.changeBreadcrumbs({ schema: this.schema });
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.originalRoutine = {
sql: this.customizations.procedureSql,
language: this.customizations.languages ? this.customizations.languages[0] : null,
name: '',
definer: '',
comment: '',
security: 'DEFINER',
dataAccess: 'CONTAINS SQL',
deterministic: false
};
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine)); const customizations = computed(() => {
return workspace.value.customizations;
});
setTimeout(() => { const isChanged = computed(() => {
this.resizeQueryEditor(); return JSON.stringify(originalRoutine.value) !== JSON.stringify(localRoutine.value);
}, 50); });
window.addEventListener('keydown', this.onKey); const saveChanges = async () => {
}, if (isSaving.value) return;
mounted () { isSaving.value = true;
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => {
this.$refs.firstInput.focus();
}, 100);
window.addEventListener('resize', this.resizeQueryEditor);
},
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = { const params = {
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
...this.localRoutine ...localRoutine.value
}; };
try { try {
const { status, response } = await Routines.createRoutine(params); const { status, response } = await Routines.createRoutine(params);
if (status === 'success') { if (status === 'success') {
await this.refreshStructure(this.connection.uid); await refreshStructure(props.connection.uid);
this.newTab({ newTab({
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
elementName: this.localRoutine.name, elementName: localRoutine.value.name,
elementType: 'routine', elementType: 'routine',
type: 'routine-props' type: 'routine-props'
}); });
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid }); removeTab({ uid: props.connection.uid, tab: props.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, routine: this.localRoutine.name }); changeBreadcrumbs({ schema: props.schema, routine: localRoutine.value.name });
} }
else else
this.addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
} }
this.isSaving = false; isSaving.value = false;
}, };
clearChanges () {
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine)); const clearChanges = () => {
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql); localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
}, queryEditor.value.editor.session.setValue(localRoutine.value.sql);
resizeQueryEditor () { };
if (this.$refs.queryEditor) {
const resizeQueryEditor = () => {
if (queryEditor.value) {
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight; const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size; editorHeight.value = size;
this.$refs.queryEditor.editor.resize(); queryEditor.value.editor.resize();
} }
}, };
parametersUpdate (parameters) {
this.localRoutine = { ...this.localRoutine, parameters }; const parametersUpdate = (parameters: FunctionParam[]) => {
}, localRoutine.value = { ...localRoutine.value, parameters };
showParamsModal () { };
this.isParamsModal = true;
}, const showParamsModal = () => {
hideParamsModal () { isParamsModal.value = true;
this.isParamsModal = false; };
},
showAskParamsModal () { const hideParamsModal = () => {
this.isAskingParameters = true; isParamsModal.value = false;
}, };
hideAskParamsModal () {
this.isAskingParameters = false; const onKey = (e: KeyboardEvent) => {
}, if (props.isSelected) {
onKey (e) {
if (this.isSelected) {
e.stopPropagation(); e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S if (e.ctrlKey && e.key === 's') { // CTRL + S
if (this.isChanged) if (isChanged.value)
this.saveChanges(); saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalRoutine.value = {
sql: customizations.value.functionSql,
language: customizations.value.languages ? customizations.value.languages[0] : null,
name: '',
definer: '',
comment: '',
security: 'DEFINER',
dataAccess: 'CONTAINS SQL',
deterministic: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
returns: workspace.value.dataTypes.length ? (workspace.value.dataTypes[0] as any).types[0].name : null
};
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
</script> </script>

View File

@ -11,26 +11,26 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showTimingModal"> <button class="btn btn-dark btn-sm" @click="showTimingModal">
<i class="mdi mdi-24px mdi-timer mr-1" /> <i class="mdi mdi-24px mdi-timer mr-1" />
<span>{{ $t('word.timing') }}</span> <span>{{ t('word.timing') }}</span>
</button> </button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
@ -40,7 +40,7 @@
<div class="columns"> <div class="columns">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
<input <input
ref="firstInput" ref="firstInput"
v-model="localScheduler.name" v-model="localScheduler.name"
@ -51,19 +51,19 @@
</div> </div>
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label> <label class="form-label">{{ t('word.definer') }}</label>
<BaseSelect <BaseSelect
v-model="localScheduler.definer" v-model="localScheduler.definer"
:options="users" :options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.comment') }}</label> <label class="form-label">{{ t('word.comment') }}</label>
<input <input
v-model="localScheduler.comment" v-model="localScheduler.comment"
class="form-input" class="form-input"
@ -73,7 +73,7 @@
</div> </div>
<div class="column"> <div class="column">
<div class="form-group"> <div class="form-group">
<label class="form-label mr-2">{{ $t('word.state') }}</label> <label class="form-label mr-2">{{ t('word.state') }}</label>
<label class="form-radio form-inline"> <label class="form-radio form-inline">
<input <input
v-model="localScheduler.state" v-model="localScheduler.state"
@ -104,7 +104,7 @@
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.schedulerBody') }}</label> <label class="form-label ml-2">{{ t('message.schedulerBody') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
ref="queryEditor" ref="queryEditor"
@ -124,109 +124,153 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { storeToRefs } from 'pinia'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import { EventInfos } from 'common/interfaces/antares';
import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal'; import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue';
import Schedulers from '@/ipc-api/Schedulers'; import Schedulers from '@/ipc-api/Schedulers';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewScheduler',
components: { const props = defineProps({
BaseLoader,
QueryEditor,
WorkspaceTabPropsSchedulerTimingModal,
BaseSelect
},
props: {
tabUid: String, tabUid: String,
connection: Object, connection: Object,
tab: Object, tab: Object,
isSelected: Boolean, isSelected: Boolean,
schema: String schema: String
}, });
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { const {
getWorkspace, getWorkspace,
refreshStructure, refreshStructure,
changeBreadcrumbs, changeBreadcrumbs,
setUnsavedChanges, setUnsavedChanges,
newTab, newTab,
removeTab, removeTab
renameTabs } = workspacesStore;
} = workspacesStore;
return { const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
addNotification, const firstInput: Ref<HTMLInputElement> = ref(null);
selectedWorkspace, const isLoading = ref(false);
getWorkspace, const isSaving = ref(false);
refreshStructure, const isTimingModal = ref(false);
changeBreadcrumbs, const originalScheduler = ref(null);
setUnsavedChanges, const localScheduler = ref(null);
newTab, const editorHeight = ref(300);
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
isTimingModal: false,
originalScheduler: {},
localScheduler: {},
lastScheduler: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
isChanged () {
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
},
isDefinerInUsers () {
return this.originalScheduler ? this.workspace.users.some(user => this.originalScheduler.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const workspace = computed(() => {
}, return getWorkspace(props.connection.uid);
users () { });
const users = [{ value: '' }, ...this.workspace.users];
if (!this.isDefinerInUsers) { const isChanged = computed(() => {
const [name, host] = this.originalScheduler.definer.replaceAll('`', '').split('@'); return JSON.stringify(originalScheduler.value) !== JSON.stringify(localScheduler.value);
});
const isDefinerInUsers = computed(() => {
return originalScheduler.value ? workspace.value.users.some(user => originalScheduler.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
});
const users = computed(() => {
const users = [{ value: '' }, ...workspace.value.users];
if (!isDefinerInUsers.value) {
const [name, host] = originalScheduler.value.definer.replaceAll('`', '').split('@');
users.unshift({ name, host }); users.unshift({ name, host });
} }
return users; return users;
});
const saveChanges = async () => {
if (isSaving.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
schema: props.schema,
...localScheduler.value
};
try {
const { status, response } = await Schedulers.createScheduler(params);
if (status === 'success') {
await refreshStructure(props.connection.uid);
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: localScheduler.value.name,
elementType: 'scheduler',
type: 'scheduler-props'
});
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
changeBreadcrumbs({ schema: props.schema, scheduler: localScheduler.value.name });
} }
}, else
watch: { addNotification({ status: 'error', message: response });
isSelected (val) {
if (val)
this.changeBreadcrumbs({ schema: this.schema });
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
} }
}, catch (err) {
async created () { addNotification({ status: 'error', message: err.stack });
this.originalScheduler = { }
isSaving.value = false;
};
const clearChanges = () => {
localScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
queryEditor.value.editor.session.setValue(localScheduler.value.sql);
};
const resizeQueryEditor = () => {
if (queryEditor.value) {
const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
editorHeight.value = size;
queryEditor.value.editor.resize();
}
};
const showTimingModal = () => {
isTimingModal.value = true;
};
const hideTimingModal = () => {
isTimingModal.value = false;
};
const timingUpdate = (options: EventInfos) => {
localScheduler.value = options;
};
const onKey = (e: KeyboardEvent) => {
if (props.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.key === 's') { // CTRL + S
if (isChanged.value)
saveChanges();
}
}
};
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalScheduler.value = {
definer: '', definer: '',
sql: 'BEGIN\r\n\r\nEND', sql: 'BEGIN\r\n\r\nEND',
name: '', name: '',
@ -235,98 +279,31 @@ export default {
every: ['1', 'DAY'], every: ['1', 'DAY'],
preserve: true, preserve: true,
state: 'DISABLE' state: 'DISABLE'
};
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
setTimeout(() => {
this.resizeQueryEditor();
}, 50);
window.addEventListener('keydown', this.onKey);
},
mounted () {
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => {
this.$refs.firstInput.focus();
}, 100);
window.addEventListener('resize', this.resizeQueryEditor);
},
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
...this.localScheduler
};
try {
const { status, response } = await Schedulers.createScheduler(params);
if (status === 'success') {
await this.refreshStructure(this.connection.uid);
this.newTab({
uid: this.connection.uid,
schema: this.schema,
elementName: this.localScheduler.name,
elementType: 'scheduler',
type: 'scheduler-props'
});
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.localScheduler.name });
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
},
clearChanges () {
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
},
resizeQueryEditor () {
if (this.$refs.queryEditor) {
const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
showTimingModal () {
this.isTimingModal = true;
},
hideTimingModal () {
this.isTimingModal = false;
},
timingUpdate (options) {
this.localScheduler = options;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
}; };
originalScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
</script> </script>

View File

@ -11,16 +11,16 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged || isSaving" :disabled="!isChanged || isSaving"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
@ -28,20 +28,20 @@
<button <button
:disabled="isSaving" :disabled="isSaving"
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('message.addNewField')" :title="t('message.addNewField')"
@click="addField" @click="addField"
> >
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> <i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $t('word.add') }}</span> <span>{{ t('word.add') }}</span>
</button> </button>
<button <button
:disabled="isSaving || !localFields.length" :disabled="isSaving || !localFields.length"
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('message.manageIndexes')" :title="t('message.manageIndexes')"
@click="showIntdexesModal" @click="showIntdexesModal"
> >
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" /> <i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
<span>{{ $t('word.indexes') }}</span> <span>{{ t('word.indexes') }}</span>
</button> </button>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
@ -49,11 +49,11 @@
@click="showForeignModal" @click="showForeignModal"
> >
<i class="mdi mdi-24px mdi-key-link mr-1" /> <i class="mdi mdi-24px mdi-key-link mr-1" />
<span>{{ $t('word.foreignKeys') }}</span> <span>{{ t('word.foreignKeys') }}</span>
</button> </button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
@ -63,7 +63,7 @@
<div class="columns mb-4"> <div class="columns mb-4">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
<input <input
ref="firstInput" ref="firstInput"
v-model="localOptions.name" v-model="localOptions.name"
@ -74,7 +74,7 @@
</div> </div>
<div v-if="workspace.customizations.comment" class="column"> <div v-if="workspace.customizations.comment" class="column">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.comment') }}</label> <label class="form-label">{{ t('word.comment') }}</label>
<input <input
v-model="localOptions.comment" v-model="localOptions.comment"
class="form-input" class="form-input"
@ -86,7 +86,7 @@
<div v-if="workspace.customizations.collations" class="column col-auto"> <div v-if="workspace.customizations.collations" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.collation') }} {{ t('word.collation') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localOptions.collation" v-model="localOptions.collation"
@ -100,7 +100,7 @@
<div v-if="workspace.customizations.engines" class="column col-auto"> <div v-if="workspace.customizations.engines" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.engine') }} {{ t('word.engine') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localOptions.engine" v-model="localOptions.engine"
@ -160,264 +160,221 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import Tables from '@/ipc-api/Tables'; import Tables from '@/ipc-api/Tables';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields'; import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue';
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal'; import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal'; import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue';
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState'; import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewTable',
components: { const props = defineProps({
BaseLoader,
WorkspaceTabPropsTableFields,
WorkspaceTabPropsTableIndexesModal,
WorkspaceTabPropsTableForeignModal,
WorkspaceTabNewTableEmptyState,
BaseSelect
},
props: {
tabUid: String, tabUid: String,
connection: Object, connection: Object as Prop<ConnectionParams>,
tab: Object, tab: Object,
isSelected: Boolean, isSelected: Boolean,
schema: String schema: String
}, });
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace, getWorkspace,
getDatabaseVariable, getDatabaseVariable,
refreshStructure, refreshStructure,
setUnsavedChanges, setUnsavedChanges,
newTab, newTab,
renameTabs,
removeTab, removeTab,
changeBreadcrumbs changeBreadcrumbs
} = workspacesStore; } = workspacesStore;
return { const indexTable: Ref<Component & {$refs: { tableWrapper: HTMLDivElement }}> = ref(null);
addNotification, const firstInput: Ref<HTMLInputElement> = ref(null);
getWorkspace, const isLoading = ref(false);
getDatabaseVariable, const isSaving = ref(false);
refreshStructure, const isIndexesModal = ref(false);
setUnsavedChanges, const isForeignModal = ref(false);
newTab, const originalFields: Ref<TableField[]> = ref([]);
renameTabs, const localFields: Ref<TableField[]> = ref([]);
removeTab, const originalKeyUsage: Ref<TableForeign[]> = ref([]);
changeBreadcrumbs, const localKeyUsage: Ref<TableForeign[]> = ref([]);
selectedWorkspace const originalIndexes: Ref<TableIndex[]> = ref([]);
}; const localIndexes: Ref<TableIndex[]> = ref([]);
}, const tableOptions: Ref<TableOptions> = ref(null);
data () { const localOptions: Ref<TableOptions> = ref(null);
return { const newFieldsCounter = ref(0);
isLoading: false,
isSaving: false, const workspace = computed(() => {
isIndexesModal: false, return getWorkspace(props.connection.uid);
isForeignModal: false, });
isOptionsChanging: false,
originalFields: [], const defaultCollation = computed(() => {
localFields: [], if (workspace.value.customizations.collations)
originalKeyUsage: [], return getDatabaseVariable(selectedWorkspace.value, 'collation_server')?.value || '';
localKeyUsage: [],
originalIndexes: [],
localIndexes: [],
tableOptions: {},
localOptions: {},
lastTable: null,
newFieldsCounter: 0
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
defaultCollation () {
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server')?.value || '';
return ''; return '';
}, });
defaultEngine () {
if (this.workspace.customizations.engines) const defaultEngine = computed(() => {
return this.workspace.engines?.find(engine => engine.isDefault)?.name || ''; if (workspace.value.customizations.engines)
return workspace.value.engines?.find(engine => engine.isDefault)?.name as string || '';
return ''; return '';
}, });
schemaTables () {
const schemaTables = this.workspace.structure const schemaTables = computed(() => {
.filter(schema => schema.name === this.schema) const schemaTables = workspace.value.structure
.filter(schema => schema.name === props.schema)
.map(schema => schema.tables); .map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
}, });
isChanged () {
return JSON.stringify(this.originalFields) !== JSON.stringify(this.localFields) ||
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) ||
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions);
},
isValid () {
return !!this.localFields.length && !!this.localOptions.name.trim().length;
}
},
watch: {
isSelected (val) {
if (val)
this.changeBreadcrumbs({ schema: this.schema });
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.tableOptions = {
name: '',
type: 'table',
engine: this.defaultEngine,
comment: '',
collation: this.defaultCollation
};
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions)); const isChanged = computed(() => {
window.addEventListener('keydown', this.onKey); return JSON.stringify(originalFields.value) !== JSON.stringify(localFields.value) ||
}, JSON.stringify(originalKeyUsage.value) !== JSON.stringify(localKeyUsage.value) ||
mounted () { JSON.stringify(originalIndexes.value) !== JSON.stringify(localIndexes.value) ||
if (this.isSelected) JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value);
this.changeBreadcrumbs({ schema: this.schema }); });
setTimeout(() => { const isValid = computed(() => {
this.$refs.firstInput.focus(); return !!localFields.value.length && !!localOptions.value.name.trim().length;
}, 100); });
},
beforeUnmount () { const saveChanges = async () => {
window.removeEventListener('keydown', this.onKey); if (isSaving.value || !isValid.value) return;
}, isSaving.value = true;
methods: {
async saveChanges () {
if (this.isSaving || !this.isValid) return;
this.isSaving = true;
const params = { const params = {
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
fields: this.localFields, fields: localFields.value,
foreigns: this.localKeyUsage, foreigns: localKeyUsage.value,
indexes: this.localIndexes, indexes: localIndexes.value,
options: this.localOptions options: localOptions.value
}; };
try { try {
const { status, response } = await Tables.createTable(params); const { status, response } = await Tables.createTable(params);
if (status === 'success') { if (status === 'success') {
await this.refreshStructure(this.connection.uid); await refreshStructure(props.connection.uid);
this.newTab({ newTab({
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
elementName: this.localOptions.name, elementName: localOptions.value.name,
elementType: 'table', elementType: 'table',
type: 'table-props' type: 'table-props'
}); });
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid }); removeTab({ uid: props.connection.uid, tab: props.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, table: this.localOptions.name }); changeBreadcrumbs({ schema: props.schema, table: localOptions.value.name });
} }
else else
this.addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
} }
this.isSaving = false; isSaving.value = false;
this.newFieldsCounter = 0; newFieldsCounter.value = 0;
}, };
clearChanges () {
this.localFields = JSON.parse(JSON.stringify(this.originalFields));
this.localIndexes = JSON.parse(JSON.stringify(this.originalIndexes));
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
this.tableOptions = { const clearChanges = () => {
localFields.value = JSON.parse(JSON.stringify(originalFields.value));
localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value));
localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value));
tableOptions.value = {
name: '', name: '',
type: 'table', type: 'table',
engine: this.defaultEngine, engine: defaultEngine.value,
comment: '', comment: '',
collation: this.defaultCollation collation: defaultCollation.value
}; };
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions)); localOptions.value = JSON.parse(JSON.stringify(tableOptions.value));
this.newFieldsCounter = 0; newFieldsCounter.value = 0;
}, };
addField () {
this.localFields.push({ const addField = () => {
localFields.value.push({
_antares_id: uidGen(), _antares_id: uidGen(),
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`, name: `${t('word.field', 1)}_${++newFieldsCounter.value}`,
key: '', key: '',
type: this.workspace.dataTypes[0].types[0].name, // eslint-disable-next-line @typescript-eslint/no-explicit-any
schema: this.schema, type: (workspace.value.dataTypes[0] as any).types[0].name,
schema: props.schema,
numPrecision: null, numPrecision: null,
numLength: this.workspace.dataTypes[0].types[0].length, // eslint-disable-next-line @typescript-eslint/no-explicit-any
numLength: (workspace.value.dataTypes[0] as any).types[0].length,
datePrecision: null, datePrecision: null,
charLength: null, charLength: null,
nullable: false, nullable: false,
unsigned: false, unsigned: false,
zerofill: false, zerofill: false,
order: this.localFields.length + 1, order: localFields.value.length + 1,
default: null, default: null,
charset: null, charset: null,
collation: null, collation: defaultCollation.value,
autoIncrement: false, autoIncrement: false,
onUpdate: '', onUpdate: '',
comment: '' comment: ''
}); });
setTimeout(() => { setTimeout(() => {
const scrollable = this.$refs.indexTable.$refs.tableWrapper; const scrollable = indexTable.value.$refs.tableWrapper;
scrollable.scrollTop = scrollable.scrollHeight + 30; scrollable.scrollTop = scrollable.scrollHeight + 30;
}, 20); }, 20);
}, };
renameField (payload) {
this.localIndexes = this.localIndexes.map(index => { const renameField = (payload: {index: string; new: string; old: string}) => {
localIndexes.value = localIndexes.value.map(index => {
const fi = index.fields.findIndex(field => field === payload.old); const fi = index.fields.findIndex(field => field === payload.old);
if (fi !== -1) if (fi !== -1)
index.fields[fi] = payload.new; index.fields[fi] = payload.new;
return index; return index;
}); });
this.localKeyUsage = this.localKeyUsage.map(key => { localKeyUsage.value = localKeyUsage.value.map(key => {
if (key.field === payload.old) if (key.field === payload.old)
key.field = payload.new; key.field = payload.new;
return key; return key;
}); });
}, };
duplicateField (uid) {
const fieldToClone = Object.assign({}, this.localFields.find(field => field._antares_id === uid)); const duplicateField = (uid: string) => {
const fieldToClone = Object.assign({}, localFields.value.find(field => field._antares_id === uid));
fieldToClone._antares_id = uidGen(); fieldToClone._antares_id = uidGen();
fieldToClone.name = `${fieldToClone.name}_copy`; fieldToClone.name = `${fieldToClone.name}_copy`;
fieldToClone.order = this.localFields.length + 1; fieldToClone.order = localFields.value.length + 1;
this.localFields = [...this.localFields, fieldToClone]; localFields.value = [...localFields.value, fieldToClone];
setTimeout(() => { setTimeout(() => {
const scrollable = this.$refs.indexTable.$refs.tableWrapper; const scrollable = indexTable.value.$refs.tableWrapper;
scrollable.scrollTop = scrollable.scrollHeight + 30; scrollable.scrollTop = scrollable.scrollHeight + 30;
}, 20); }, 20);
}, };
removeField (uid) {
this.localFields = this.localFields.filter(field => field._antares_id !== uid); const removeField = (uid: string) => {
}, localFields.value = localFields.value.filter(field => field._antares_id !== uid);
addNewIndex (payload) { };
this.localIndexes = [...this.localIndexes, {
const addNewIndex = (payload: { index: string; field: string }) => {
localIndexes.value = [...localIndexes.value, {
_antares_id: uidGen(), _antares_id: uidGen(),
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field, name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
fields: [payload.field], fields: [payload.field],
@ -427,43 +384,78 @@ export default {
indexComment: '', indexComment: '',
cardinality: 0 cardinality: 0
}]; }];
}, };
addToIndex (payload) {
this.localIndexes = this.localIndexes.map(index => { const addToIndex = (payload: { index: string; field: string }) => {
localIndexes.value = localIndexes.value.map(index => {
if (index._antares_id === payload.index) index.fields.push(payload.field); if (index._antares_id === payload.index) index.fields.push(payload.field);
return index; return index;
}); });
}, };
optionsUpdate (options) {
this.localOptions = options; const showIntdexesModal = () => {
}, isIndexesModal.value = true;
showIntdexesModal () { };
this.isIndexesModal = true;
}, const hideIndexesModal = () => {
hideIndexesModal () { isIndexesModal.value = false;
this.isIndexesModal = false; };
},
indexesUpdate (indexes) { const indexesUpdate = (indexes: TableIndex[]) => {
this.localIndexes = indexes; localIndexes.value = indexes;
}, };
showForeignModal () {
this.isForeignModal = true; const showForeignModal = () => {
}, isForeignModal.value = true;
hideForeignModal () { };
this.isForeignModal = false;
}, const hideForeignModal = () => {
foreignsUpdate (foreigns) { isForeignModal.value = false;
this.localKeyUsage = foreigns; };
},
onKey (e) { const foreignsUpdate = (foreigns: TableForeign[]) => {
if (this.isSelected) { localKeyUsage.value = foreigns;
};
const onKey = (e: KeyboardEvent) => {
if (props.isSelected) {
e.stopPropagation(); e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S if (e.ctrlKey && e.key === 's') { // CTRL + S
if (this.isChanged) if (isChanged.value)
this.saveChanges(); saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
tableOptions.value = {
name: '',
type: 'table',
engine: defaultEngine.value,
comment: '',
collation: defaultCollation.value
};
localOptions.value = JSON.parse(JSON.stringify(tableOptions.value));
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>

View File

@ -1,22 +1,22 @@
<template> <template>
<div class="column col-12 empty"> <div class="column col-12 empty">
<p class="h6 empty-subtitle"> <p class="h6 empty-subtitle">
{{ $t('message.thereAreNoTableFields') }} {{ t('message.thereAreNoTableFields') }}
</p> </p>
<div class="empty-action"> <div class="empty-action">
<button class="btn btn-gray d-flex" @click="$emit('new-field')"> <button class="btn btn-gray d-flex" @click="emit('new-field')">
<i class="mdi mdi-24px mdi-playlist-plus mr-2" /> <i class="mdi mdi-24px mdi-playlist-plus mr-2" />
{{ $t('message.addNewField') }} {{ t('message.addNewField') }}
</button> </button>
</div> </div>
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { useI18n } from 'vue-i18n';
name: 'WorkspaceTabNewTableEmptyState',
emits: ['new-field'] const { t } = useI18n();
}; const emit = defineEmits(['new-field']);
</script> </script>
<style scoped> <style scoped>

View File

@ -11,20 +11,20 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
@ -34,7 +34,7 @@
<div class="columns"> <div class="columns">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
<input <input
ref="firstInput" ref="firstInput"
v-model="localTrigger.name" v-model="localTrigger.name"
@ -45,12 +45,12 @@
</div> </div>
<div v-if="customizations.definer" class="column col-auto"> <div v-if="customizations.definer" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label> <label class="form-label">{{ t('word.definer') }}</label>
<BaseSelect <BaseSelect
v-model="localTrigger.definer" v-model="localTrigger.definer"
:options="users" :options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
@ -58,7 +58,7 @@
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename"> <fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.table') }}</label> <label class="form-label">{{ t('word.table') }}</label>
<BaseSelect <BaseSelect
v-model="localTrigger.table" v-model="localTrigger.table"
:options="schemaTables" :options="schemaTables"
@ -70,7 +70,7 @@
</div> </div>
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.event') }}</label> <label class="form-label">{{ t('word.event') }}</label>
<div class="input-group"> <div class="input-group">
<BaseSelect <BaseSelect
v-model="localTrigger.activation" v-model="localTrigger.activation"
@ -88,9 +88,9 @@
v-for="event in Object.keys(localEvents)" v-for="event in Object.keys(localEvents)"
:key="event" :key="event"
class="form-checkbox form-inline" class="form-checkbox form-inline"
@change.prevent="changeEvents(event)" @change.prevent="changeEvents(event as 'INSERT' | 'UPDATE' | 'DELETE')"
> >
<input :checked="localEvents[event]" type="checkbox"><i class="form-icon" /> {{ event }} <input :checked="localEvents[event as 'INSERT' | 'UPDATE' | 'DELETE']" type="checkbox"><i class="form-icon" /> {{ event }}
</label> </label>
</div> </div>
</div> </div>
@ -101,7 +101,7 @@
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.triggerStatement') }}</label> <label class="form-label ml-2">{{ t('message.triggerStatement') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
ref="queryEditor" ref="queryEditor"
@ -114,219 +114,204 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { storeToRefs } from 'pinia'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import Triggers from '@/ipc-api/Triggers'; import Triggers from '@/ipc-api/Triggers';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewTrigger',
components: { const props = defineProps({
BaseLoader,
QueryEditor,
BaseSelect
},
props: {
tabUid: String, tabUid: String,
connection: Object, connection: Object,
tab: Object, tab: Object,
isSelected: Boolean, isSelected: Boolean,
schema: String schema: String
}, });
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { const {
getWorkspace, getWorkspace,
refreshStructure, refreshStructure,
changeBreadcrumbs, changeBreadcrumbs,
setUnsavedChanges, setUnsavedChanges,
newTab, newTab,
removeTab, removeTab
renameTabs } = workspacesStore;
} = workspacesStore;
return { const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
addNotification, const firstInput: Ref<HTMLInputElement> = ref(null);
selectedWorkspace, const isLoading = ref(false);
getWorkspace, const isSaving = ref(false);
refreshStructure, const originalTrigger = ref(null);
changeBreadcrumbs, const localTrigger = ref(null);
setUnsavedChanges, const editorHeight = ref(300);
newTab, const localEvents = ref({ INSERT: false, UPDATE: false, DELETE: false });
removeTab,
renameTabs const workspace = computed(() => {
}; return getWorkspace(props.connection.uid);
}, });
data () {
return { const customizations = computed(() => {
isLoading: false, return workspace.value.customizations;
isSaving: false, });
originalTrigger: {},
localTrigger: {}, const isChanged = computed(() => {
lastTrigger: null, return JSON.stringify(originalTrigger.value) !== JSON.stringify(localTrigger.value);
sqlProxy: '', });
editorHeight: 300,
localEvents: { INSERT: false, UPDATE: false, DELETE: false } const isDefinerInUsers = computed(() => {
}; return originalTrigger.value ? workspace.value.users.some(user => originalTrigger.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
}, });
computed: {
workspace () { const schemaTables = computed(() => {
return this.getWorkspace(this.connection.uid); const schemaTables = workspace.value.structure
}, .filter(schema => schema.name === props.schema)
customizations () {
return this.workspace.customizations;
},
isChanged () {
return JSON.stringify(this.originalTrigger) !== JSON.stringify(this.localTrigger);
},
isDefinerInUsers () {
return this.originalTrigger ? this.workspace.users.some(user => this.originalTrigger.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables); .map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
}, });
users () {
const users = [{ value: '' }, ...this.workspace.users]; const users = computed(() => {
if (!this.isDefinerInUsers) { const users = [{ value: '' }, ...workspace.value.users];
const [name, host] = this.originalTrigger.definer.replaceAll('`', '').split('@'); if (!isDefinerInUsers.value) {
const [name, host] = originalTrigger.value.definer.replaceAll('`', '').split('@');
users.unshift({ name, host }); users.unshift({ name, host });
} }
return users; return users;
} });
},
watch: {
isSelected (val) {
if (val)
this.changeBreadcrumbs({ schema: this.schema });
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.originalTrigger = {
sql: this.customizations.triggerSql,
definer: '',
table: this.schemaTables.length ? this.schemaTables[0].name : null,
activation: 'BEFORE',
event: this.customizations.triggerMultipleEvents ? ['INSERT'] : 'INSERT',
name: ''
};
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger)); const changeEvents = (event: 'INSERT' | 'UPDATE' | 'DELETE') => {
if (customizations.value.triggerMultipleEvents) {
setTimeout(() => { localEvents.value[event] = !localEvents.value[event];
this.resizeQueryEditor(); localTrigger.value.event = [];
}, 50); for (const key in localEvents.value) {
if (localEvents.value[key as 'INSERT' | 'UPDATE' | 'DELETE'])
window.addEventListener('keydown', this.onKey); localTrigger.value.event.push(key);
},
mounted () {
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => {
this.$refs.firstInput.focus();
}, 100);
window.addEventListener('resize', this.resizeQueryEditor);
},
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
changeEvents (event) {
if (this.customizations.triggerMultipleEvents) {
this.localEvents[event] = !this.localEvents[event];
this.localTrigger.event = [];
for (const key in this.localEvents) {
if (this.localEvents[key])
this.localTrigger.event.push(key);
} }
} }
}, };
async saveChanges () {
if (this.isSaving) return; const saveChanges = async () => {
this.isSaving = true; if (isSaving.value) return;
isSaving.value = true;
const params = { const params = {
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
...this.localTrigger ...localTrigger.value
}; };
try { try {
const { status, response } = await Triggers.createTrigger(params); const { status, response } = await Triggers.createTrigger(params);
if (status === 'success') { if (status === 'success') {
await this.refreshStructure(this.connection.uid); await refreshStructure(props.connection.uid);
this.newTab({ newTab({
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
elementName: this.localTrigger.name, elementName: localTrigger.value.name,
elementType: 'trigger', elementType: 'trigger',
type: 'trigger-props' type: 'trigger-props'
}); });
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid }); removeTab({ uid: props.connection.uid, tab: props.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, trigger: this.localTrigger.name }); changeBreadcrumbs({ schema: props.schema, trigger: localTrigger.value.name });
} }
else else
this.addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
} }
this.isSaving = false; isSaving.value = false;
}, };
clearChanges () {
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger));
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
Object.keys(this.localEvents).forEach(event => { const clearChanges = () => {
this.localEvents[event] = false; localTrigger.value = JSON.parse(JSON.stringify(originalTrigger.value));
queryEditor.value.editor.session.setValue(localTrigger.value.sql);
Object.keys(localEvents.value).forEach((event: 'INSERT' | 'UPDATE' | 'DELETE') => {
localEvents.value[event] = false;
}); });
if (this.customizations.triggerMultipleEvents) { if (customizations.value.triggerMultipleEvents) {
this.originalTrigger.event.forEach(e => { originalTrigger.value.event.forEach((e: 'INSERT' | 'UPDATE' | 'DELETE') => {
this.localEvents[e] = true; localEvents.value[e] = true;
}); });
} }
}, };
resizeQueryEditor () {
if (this.$refs.queryEditor) { const resizeQueryEditor = () => {
if (queryEditor.value) {
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight; const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size; editorHeight.value = size;
this.$refs.queryEditor.editor.resize(); queryEditor.value.editor.resize();
} }
}, };
onKey (e) {
if (this.isSelected) { const onKey = (e: KeyboardEvent) => {
if (props.isSelected) {
e.stopPropagation(); e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged) if (isChanged.value)
this.saveChanges(); saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalTrigger.value = {
sql: customizations.value.triggerSql,
definer: '',
table: schemaTables.value.length ? schemaTables.value[0].name : null,
activation: 'BEFORE',
event: customizations.value.triggerMultipleEvents ? ['INSERT'] : 'INSERT',
name: ''
};
localTrigger.value = JSON.parse(JSON.stringify(originalTrigger.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
window.addEventListener('resize', resizeQueryEditor);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>

View File

@ -11,16 +11,16 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
</div> </div>
</div> </div>
@ -30,7 +30,7 @@
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.name') }} {{ t('word.name') }}
</label> </label>
<input <input
ref="firstInput" ref="firstInput"
@ -43,7 +43,7 @@
<div v-if="customizations.triggerFunctionlanguages" class="column col-auto"> <div v-if="customizations.triggerFunctionlanguages" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.language') }} {{ t('word.language') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localFunction.language" v-model="localFunction.language"
@ -55,20 +55,20 @@
<div v-if="customizations.definer" class="column col-auto"> <div v-if="customizations.definer" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.definer') }} {{ t('word.definer') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localFunction.definer" v-model="localFunction.definer"
:options="workspace.users" :options="workspace.users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
</div> </div>
<div v-if="customizations.comment" class="form-group"> <div v-if="customizations.comment" class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.comment') }} {{ t('word.comment') }}
</label> </label>
<input <input
v-model="localFunction.comment" v-model="localFunction.comment"
@ -80,7 +80,7 @@
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label> <label class="form-label ml-2">{{ t('message.functionBody') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
ref="queryEditor" ref="queryEditor"
@ -93,190 +93,157 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { storeToRefs } from 'pinia'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewTriggerFunction',
components: { const props = defineProps({
BaseLoader,
QueryEditor,
BaseSelect
},
props: {
tabUid: String, tabUid: String,
connection: Object, connection: Object,
tab: Object, tab: Object,
isSelected: Boolean, isSelected: Boolean,
schema: String schema: String
}, });
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { const {
getWorkspace, getWorkspace,
refreshStructure, refreshStructure,
changeBreadcrumbs, changeBreadcrumbs,
setUnsavedChanges, setUnsavedChanges,
newTab, newTab,
removeTab, removeTab
renameTabs } = workspacesStore;
} = workspacesStore;
return { const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
addNotification, const firstInput: Ref<HTMLInputElement> = ref(null);
selectedWorkspace, const isLoading = ref(false);
getWorkspace, const isSaving = ref(false);
refreshStructure, const originalFunction = ref(null);
changeBreadcrumbs, const localFunction = ref(null);
setUnsavedChanges, const editorHeight = ref(300);
newTab,
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
isParamsModal: false,
isAskingParameters: false,
originalFunction: {},
localFunction: {},
lastFunction: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
},
isDefinerInUsers () {
return this.originalFunction
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
: true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const workspace = computed(() => {
} return getWorkspace(props.connection.uid);
}, });
watch: {
isSelected (val) {
if (val)
this.changeBreadcrumbs({ schema: this.schema });
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.originalFunction = {
sql: this.customizations.triggerFunctionSql,
language: this.customizations.triggerFunctionlanguages.length ? this.customizations.triggerFunctionlanguages[0] : null,
name: ''
};
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction)); const customizations = computed(() => {
return workspace.value.customizations;
});
setTimeout(() => { const isChanged = computed(() => {
this.resizeQueryEditor(); return JSON.stringify(originalFunction.value) !== JSON.stringify(localFunction.value);
}, 50); });
window.addEventListener('keydown', this.onKey); const saveChanges = async () => {
}, if (isSaving.value) return;
mounted () { isSaving.value = true;
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => {
this.$refs.firstInput.focus();
}, 100);
window.addEventListener('resize', this.resizeQueryEditor);
},
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = { const params = {
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
...this.localFunction ...localFunction.value
}; };
try { try {
const { status, response } = await Functions.createTriggerFunction(params); const { status, response } = await Functions.createTriggerFunction(params);
if (status === 'success') { if (status === 'success') {
await this.refreshStructure(this.connection.uid); await refreshStructure(props.connection.uid);
this.newTab({ newTab({
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
elementName: this.localFunction.name, elementName: localFunction.value.name,
elementType: 'triggerFunction', elementType: 'triggerFunction',
type: 'trigger-function-props' type: 'trigger-function-props'
}); });
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid }); removeTab({ uid: props.connection.uid, tab: props.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, triggerFunction: this.localFunction.name }); changeBreadcrumbs({ schema: props.schema, triggerFunction: localFunction.value.name });
} }
else else
this.addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
} }
this.isSaving = false; isSaving.value = false;
}, };
clearChanges () {
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction)); const clearChanges = () => {
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql); localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
}, queryEditor.value.editor.session.setValue(localFunction.value.sql);
resizeQueryEditor () { };
if (this.$refs.queryEditor) {
const resizeQueryEditor = () => {
if (queryEditor.value) {
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight; const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size; editorHeight.value = size;
this.$refs.queryEditor.editor.resize(); queryEditor.value.editor.resize();
} }
}, };
onKey (e) {
if (this.isSelected) { const onKey = (e: KeyboardEvent) => {
if (props.isSelected) {
e.stopPropagation(); e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S if (e.ctrlKey && e.key === 's') { // CTRL + S
if (this.isChanged) if (isChanged.value)
this.saveChanges(); saveChanges();
}
}
} }
} }
}; };
originalFunction.value = {
sql: customizations.value.triggerFunctionSql,
language: customizations.value.triggerFunctionlanguages.length ? customizations.value.triggerFunctionlanguages[0] : null,
name: ''
};
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
window.addEventListener('resize', resizeQueryEditor);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
</script> </script>

View File

@ -11,20 +11,20 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
@ -34,7 +34,7 @@
<div class="columns"> <div class="columns">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
<input <input
ref="firstInput" ref="firstInput"
v-model="localView.name" v-model="localView.name"
@ -45,19 +45,19 @@
</div> </div>
<div class="column col-auto"> <div class="column col-auto">
<div v-if="workspace.customizations.definer" class="form-group"> <div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label> <label class="form-label">{{ t('word.definer') }}</label>
<BaseSelect <BaseSelect
v-model="localView.definer" v-model="localView.definer"
:options="users" :options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
</div> </div>
<div class="column col-auto mr-2"> <div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group"> <div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label> <label class="form-label">{{ t('message.sqlSecurity') }}</label>
<BaseSelect <BaseSelect
v-model="localView.security" v-model="localView.security"
:options="['DEFINER', 'INVOKER']" :options="['DEFINER', 'INVOKER']"
@ -67,7 +67,7 @@
</div> </div>
<div class="column col-auto mr-2"> <div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group"> <div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label> <label class="form-label">{{ t('word.algorithm') }}</label>
<BaseSelect <BaseSelect
v-model="localView.algorithm" v-model="localView.algorithm"
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']" :options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
@ -77,10 +77,10 @@
</div> </div>
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2"> <div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label> <label class="form-label">{{ t('message.updateOption') }}</label>
<BaseSelect <BaseSelect
v-model="localView.updateOption" v-model="localView.updateOption"
:option-track-by="(user) => user.value" :option-track-by="(user: any) => user.value"
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]" :options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
class="form-select" class="form-select"
/> />
@ -90,7 +90,7 @@
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label> <label class="form-label ml-2">{{ t('message.selectStatement') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
ref="queryEditor" ref="queryEditor"
@ -103,194 +103,178 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { storeToRefs } from 'pinia'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import Views from '@/ipc-api/Views'; import Views from '@/ipc-api/Views';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewView',
components: { const props = defineProps({
BaseLoader,
QueryEditor,
BaseSelect
},
props: {
tabUid: String, tabUid: String,
connection: Object, connection: Object,
tab: Object, tab: Object,
isSelected: Boolean, isSelected: Boolean,
schema: String schema: String
}, });
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { const {
getWorkspace, getWorkspace,
refreshStructure, refreshStructure,
setUnsavedChanges, setUnsavedChanges,
changeBreadcrumbs, changeBreadcrumbs,
newTab, newTab,
removeTab, removeTab
renameTabs } = workspacesStore;
} = workspacesStore;
return { const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
addNotification, const firstInput: Ref<HTMLInputElement> = ref(null);
selectedWorkspace, const isLoading = ref(false);
getWorkspace, const isSaving = ref(false);
refreshStructure, const originalView = ref(null);
setUnsavedChanges, const localView = ref(null);
changeBreadcrumbs, const editorHeight = ref(300);
newTab,
removeTab, const workspace = computed(() => {
renameTabs return getWorkspace(props.connection.uid);
}; });
},
data () { const isChanged = computed(() => {
return { return JSON.stringify(originalView.value) !== JSON.stringify(localView.value);
isLoading: false, });
isSaving: false,
originalView: {}, const isDefinerInUsers = computed(() => {
localView: {}, return originalView.value ? workspace.value.users.some(user => originalView.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
lastView: null, });
sqlProxy: '',
editorHeight: 300 const users = computed(() => {
}; const users = [{ value: '' }, ...workspace.value.users];
}, if (!isDefinerInUsers.value) {
computed: { const [name, host] = originalView.value.definer.replaceAll('`', '').split('@');
workspace () {
return this.getWorkspace(this.connection.uid);
},
isChanged () {
return JSON.stringify(this.originalView) !== JSON.stringify(this.localView);
},
isDefinerInUsers () {
return this.originalView ? this.workspace.users.some(user => this.originalView.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
users () {
const users = [{ value: '' }, ...this.workspace.users];
if (!this.isDefinerInUsers) {
const [name, host] = this.originalView.definer.replaceAll('`', '').split('@');
users.unshift({ name, host }); users.unshift({ name, host });
} }
return users; return users;
} });
},
watch: {
isSelected (val) {
if (val) {
this.changeBreadcrumbs({ schema: this.schema, view: this.view });
setTimeout(() => { const saveChanges = async () => {
this.resizeQueryEditor(); if (isSaving.value) return;
}, 50); isSaving.value = true;
}
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
this.originalView = {
algorithm: 'UNDEFINED',
definer: '',
security: 'DEFINER',
updateOption: '',
sql: '',
name: ''
};
this.localView = JSON.parse(JSON.stringify(this.originalView));
setTimeout(() => {
this.resizeQueryEditor();
}, 50);
window.addEventListener('keydown', this.onKey);
},
mounted () {
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => {
this.$refs.firstInput.focus();
}, 100);
window.addEventListener('resize', this.resizeQueryEditor);
},
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = { const params = {
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
...this.localView ...localView.value
}; };
try { try {
const { status, response } = await Views.createView(params); const { status, response } = await Views.createView(params);
if (status === 'success') { if (status === 'success') {
await this.refreshStructure(this.connection.uid); await refreshStructure(props.connection.uid);
this.newTab({ newTab({
uid: this.connection.uid, uid: props.connection.uid,
schema: this.schema, schema: props.schema,
elementName: this.localView.name, elementName: localView.value.name,
elementType: 'view', elementType: 'view',
type: 'view-props' type: 'view-props'
}); });
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid }); removeTab({ uid: props.connection.uid, tab: props.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, view: this.localView.name }); changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
} }
else else
this.addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
} }
this.isSaving = false; isSaving.value = false;
}, };
clearChanges () {
this.localView = JSON.parse(JSON.stringify(this.originalView)); const clearChanges = () => {
this.$refs.queryEditor.editor.session.setValue(this.localView.sql); localView.value = JSON.parse(JSON.stringify(originalView.value));
}, queryEditor.value.editor.session.setValue(localView.value.sql);
resizeQueryEditor () { };
if (this.$refs.queryEditor) {
const resizeQueryEditor = () => {
if (queryEditor.value) {
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight; const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size; editorHeight.value = size;
this.$refs.queryEditor.editor.resize(); queryEditor.value.editor.resize();
} }
}, };
onKey (e) {
if (this.isSelected) { const onKey = (e: KeyboardEvent) => {
if (props.isSelected) {
e.stopPropagation(); e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S if (e.ctrlKey && e.key === 's') { // CTRL + S
if (this.isChanged) if (isChanged.value)
this.saveChanges(); saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) {
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
setTimeout(() => {
resizeQueryEditor();
}, 50);
}
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalView.value = {
algorithm: 'UNDEFINED',
definer: '',
security: 'DEFINER',
updateOption: '',
sql: '',
name: ''
};
localView.value = JSON.parse(JSON.stringify(originalView.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
window.addEventListener('resize', resizeQueryEditor);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>

View File

@ -249,6 +249,11 @@ export default {
workspace () { workspace () {
return this.getWorkspace(this.connection.uid); return this.getWorkspace(this.connection.uid);
}, },
defaultCollation () {
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server')?.value || '';
return '';
},
defaultEngine () { defaultEngine () {
const engine = this.getDatabaseVariable(this.connection.uid, 'default_storage_engine'); const engine = this.getDatabaseVariable(this.connection.uid, 'default_storage_engine');
return engine ? engine.value : ''; return engine ? engine.value : '';
@ -440,6 +445,7 @@ export default {
const changes = []; const changes = [];
this.localFields.forEach((field, i) => { this.localFields.forEach((field, i) => {
const originalField = this.originalFields.find(oField => oField._antares_id === field._antares_id); const originalField = this.originalFields.find(oField => oField._antares_id === field._antares_id);
if (!originalField) return;
const after = i > 0 ? this.localFields[i - 1].name : false; const after = i > 0 ? this.localFields[i - 1].name : false;
const orgName = originalField.name; const orgName = originalField.name;
@ -586,7 +592,7 @@ export default {
order: this.localFields.length + 1, order: this.localFields.length + 1,
default: null, default: null,
charset: null, charset: null,
collation: null, collation: this.defaultCollation,
autoIncrement: false, autoIncrement: false,
onUpdate: '', onUpdate: '',
comment: '' comment: ''

View File

@ -124,7 +124,7 @@
</div> </div>
</template> </template>
<script> <script>// TODO: expose tableWrapper
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';

View File

@ -72,7 +72,7 @@ export interface Workspace {
structure: WorkspaceStructure[]; structure: WorkspaceStructure[];
variables: { name: string; value: string }[]; variables: { name: string; value: string }[];
collations: CollationInfos[]; collations: CollationInfos[];
users: { host: string; name: string; password: string }[]; users: { host: string; name: string; password?: string }[];
breadcrumbs: Breadcrumb; breadcrumbs: Breadcrumb;
loadingElements: { name: string; schema: string; type: string }[]; loadingElements: { name: string; schema: string; type: string }[];
loadedSchemas: Set<string>; loadedSchemas: Set<string>;