refactor: ts and composition api for base components

This commit is contained in:
Fabio Di Stasio 2022-05-10 13:02:01 +02:00
parent cc5910b88f
commit ae377a6c3c
21 changed files with 489 additions and 529 deletions

View File

@ -46,70 +46,57 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { computed, defineComponent, onBeforeUnmount } from 'vue'; import { computed, onBeforeUnmount, PropType, useSlots } from 'vue';
export default defineComponent({ const props = defineProps({
name: 'BaseConfirmModal', size: {
props: { type: String as PropType<'small' | 'medium' | '400' | 'large'>,
size: { validator: (prop: string) => ['small', 'medium', '400', 'large'].includes(prop),
type: String, default: 'small'
validator: (prop: string) => ['small', 'medium', '400', 'large'].includes(prop),
default: 'small'
},
hideFooter: {
type: Boolean,
default: false
},
confirmText: String,
cancelText: String
}, },
emits: ['confirm', 'hide'], hideFooter: {
setup (props, { slots, emit }) { type: Boolean,
const hasHeader = computed(() => !!slots.header); default: false
const hasBody = computed(() => !!slots.body); },
const hasDefault = computed(() => !!slots.default); confirmText: String,
const modalSizeClass = computed(() => { cancelText: String
if (props.size === 'small') });
return 'modal-sm'; const emit = defineEmits(['confirm', 'hide']);
if (props.size === '400') const slots = useSlots();
return 'modal-400';
else if (props.size === 'large')
return 'modal-lg';
else return '';
});
const confirmModal = () => { const hasHeader = computed(() => !!slots.header);
emit('confirm'); const hasBody = computed(() => !!slots.body);
hideModal(); const hasDefault = computed(() => !!slots.default);
}; const modalSizeClass = computed(() => {
if (props.size === 'small')
return 'modal-sm';
if (props.size === '400')
return 'modal-400';
else if (props.size === 'large')
return 'modal-lg';
else return '';
});
const hideModal = () => { const confirmModal = () => {
emit('hide'); emit('confirm');
}; hideModal();
};
const onKey = (e: KeyboardEvent) => { const hideModal = () => {
e.stopPropagation(); emit('hide');
if (e.key === 'Escape') };
hideModal();
};
window.addEventListener('keydown', onKey); const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
hideModal();
};
onBeforeUnmount(() => { window.addEventListener('keydown', onKey);
window.removeEventListener('keydown', onKey);
});
return { onBeforeUnmount(() => {
hasHeader, window.removeEventListener('keydown', onKey);
hasBody,
hasDefault,
modalSizeClass,
confirmModal,
hideModal,
onKey
};
}
}); });
</script> </script>

View File

@ -15,67 +15,60 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue';
name: 'BaseContextMenu',
props: {
contextEvent: MouseEvent
},
emits: ['close-context'],
data () {
return {
contextSize: null,
isBottom: false
};
},
computed: {
position () {
let topCord = 0;
let leftCord = 0;
if (this.contextEvent) { const contextContent: Ref<HTMLDivElement> = ref(null);
const { clientY, clientX } = this.contextEvent; const contextSize: Ref<DOMRect> = ref(null);
topCord = `${clientY + 2}px`; const isBottom: Ref<boolean> = ref(false);
leftCord = `${clientX + 5}px`; const props = defineProps<{contextEvent: MouseEvent}>();
const emit = defineEmits(['close-context']);
if (this.contextSize) { const position = computed(() => {
if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) { let topCord = '0px';
topCord = `${clientY + 3 - this.contextSize.height}px`; let leftCord = '0px';
this.isBottom = true;
}
if (clientX + this.contextSize.width + 5 >= window.innerWidth) if (props.contextEvent) {
leftCord = `${clientX - this.contextSize.width}px`; const { clientY, clientX } = props.contextEvent;
} topCord = `${clientY + 2}px`;
leftCord = `${clientX + 5}px`;
if (contextSize.value) {
if (clientY + (contextSize.value.height < 200 ? 200 : contextSize.value.height) + 5 >= window.innerHeight) {
topCord = `${clientY + 3 - contextSize.value.height}px`;
isBottom.value = true;
} }
return { if (clientX + contextSize.value.width + 5 >= window.innerWidth)
top: topCord, leftCord = `${clientX - contextSize.value.width}px`;
left: leftCord
};
}
},
created () {
window.addEventListener('keydown', this.onKey);
},
mounted () {
if (this.$refs.contextContent)
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
close () {
this.$emit('close-context');
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.close();
} }
} }
return {
top: topCord,
left: leftCord
};
});
const close = () => {
emit('close-context');
}; };
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
close();
};
window.addEventListener('keydown', onKey);
onMounted(() => {
if (contextContent.value)
contextSize.value = contextContent.value.getBoundingClientRect();
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -4,11 +4,6 @@
</div> </div>
</template> </template>
<script>
export default {
name: 'BaseLoader'
};
</script>
<style scoped> <style scoped>
.empty { .empty {
position: absolute; position: absolute;

View File

@ -1,95 +1,93 @@
<template> <template>
<div id="map" class="map" /> <div id="map" class="map" />
</template> </template>
<script>
import L from 'leaflet'; <script setup lang="ts">
import { onMounted, PropType, Ref, ref } from 'vue';
import * as L from 'leaflet';
import { import {
point, point,
lineString, lineString,
polygon polygon
} from '@turf/helpers'; } from '@turf/helpers';
import { GeoJsonObject } from 'geojson';
import { getArrayDepth } from 'common/libs/getArrayDepth'; import { getArrayDepth } from 'common/libs/getArrayDepth';
export default { interface Coordinates { x: number; y: number }
name: 'BaseMap',
props: {
points: [Object, Array],
isMultiSpatial: Boolean
},
data () {
return {
map: null,
markers: [],
center: null
};
},
mounted () {
if (this.isMultiSpatial) {
for (const element of this.points)
this.markers.push(this.getMarkers(element));
}
else {
this.markers = this.getMarkers(this.points);
if (!Array.isArray(this.points)) const props = defineProps({
this.center = [this.points.y, this.points.x]; points: [Object, Array] as PropType<Coordinates | Coordinates[]>,
} isMultiSpatial: Boolean
});
const map: Ref<L.Map> = ref(null);
const markers: Ref<GeoJsonObject | GeoJsonObject[]> = ref(null);
const center: Ref<[number, number]> = ref(null);
this.map = L.map('map', { const getMarkers = (points: Coordinates) => {
center: this.center || [0, 0], if (Array.isArray(points)) {
zoom: 15, if (getArrayDepth(points) === 1)
minZoom: 1, return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
attributionControl: false else
}); return polygon(points.map(arr => arr.reduce((acc: Coordinates[], curr: Coordinates) => [...acc, [curr.x, curr.y]], [])));
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(this.map);
const geoJsonObj = L.geoJSON(this.markers, {
style: function () {
return {
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
};
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 7,
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
});
}
}).addTo(this.map);
const southWest = L.latLng(-90, -180);
const northEast = L.latLng(90, 180);
const bounds = L.latLngBounds(southWest, northEast);
this.map.setMaxBounds(bounds);
if (!this.center) this.map.fitBounds(geoJsonObj.getBounds());
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <b>OpenStreetMap</b>'
}).addTo(this.map);
},
methods: {
getMarkers (points) {
if (Array.isArray(points)) {
if (getArrayDepth(points) === 1)
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
else
return polygon(points.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
}
else
return point([points.x, points.y]);
}
} }
else
return point([points.x, points.y]);
}; };
onMounted(() => {
if (props.isMultiSpatial) {
for (const element of props.points as Coordinates[])
(markers.value as GeoJsonObject[]).push(getMarkers(element));
}
else {
markers.value = getMarkers(props.points as Coordinates);
if (!Array.isArray(props.points))
center.value = [props.points.y, props.points.x];
}
map.value = L.map('map', {
center: center.value || [0, 0],
zoom: 15,
minZoom: 1,
attributionControl: false
});
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(map.value);
const geoJsonObj = L.geoJSON((markers.value as GeoJsonObject), {
style: function () {
return {
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
};
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 7,
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
});
}
}).addTo(map.value);
const southWest = L.latLng(-90, -180);
const northEast = L.latLng(90, 180);
const bounds = L.latLngBounds(southWest, northEast);
map.value.setMaxBounds(bounds);
if (!center.value) map.value.fitBounds(geoJsonObj.getBounds());
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <b>OpenStreetMap</b>'
}).addTo(map.value);
});
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -14,64 +14,58 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { computed, ref } from 'vue';
name: 'BaseNotification',
props: {
message: {
type: String,
default: ''
},
status: {
type: String,
default: ''
}
},
emits: ['close'],
data () {
return {
isExpanded: false
};
},
computed: {
notificationStatus () {
let className = '';
let iconName = '';
switch (this.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName }; const props = defineProps({
}, message: {
isExpandable () { type: String,
return this.message.length > 80; default: ''
}
}, },
methods: { status: {
hideToast () { type: String,
this.$emit('close'); default: ''
},
toggleExpand () {
this.isExpanded = !this.isExpanded;
}
} }
});
const isExpanded = ref(false);
const emit = defineEmits(['close']);
const notificationStatus = computed(() => {
let className = '';
let iconName = '';
switch (props.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName };
});
const isExpandable = computed(() => props.message.length > 80);
const hideToast = () => {
emit('close');
};
const toggleExpand = () => {
isExpanded.value = !isExpanded.value;
}; };
</script> </script>
<style scoped> <style scoped>
.toast { .toast {
display: flex; display: flex;

View File

@ -9,121 +9,112 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import * as ace from 'ace-builds'; import * as ace from 'ace-builds';
import { storeToRefs } from 'pinia';
import 'ace-builds/webpack-resolver'; import 'ace-builds/webpack-resolver';
import { storeToRefs } from 'pinia';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
export default { const props = defineProps({
name: 'BaseTextEditor', modelValue: String,
props: { mode: { type: String, default: 'text' },
modelValue: String, editorClass: { type: String, default: '' },
mode: { type: String, default: 'text' }, autoFocus: { type: Boolean, default: false },
editorClass: { type: String, default: '' }, readOnly: { type: Boolean, default: false },
autoFocus: { type: Boolean, default: false }, showLineNumbers: { type: Boolean, default: true },
readOnly: { type: Boolean, default: false }, height: { type: Number, default: 200 }
showLineNumbers: { type: Boolean, default: true }, });
height: { type: Number, default: 200 } const emit = defineEmits(['update:modelValue']);
}, const settingsStore = useSettingsStore();
emits: ['update:modelValue'], const mode = ref(props.mode);
setup () {
const settingsStore = useSettingsStore();
const { const {
editorTheme, editorTheme,
editorFontSize, editorFontSize,
autoComplete, autoComplete,
lineWrap lineWrap
} = storeToRefs(settingsStore); } = storeToRefs(settingsStore);
return { let editor: ace.Ace.Editor;
editorTheme, const id = uidGen();
editorFontSize,
autoComplete,
lineWrap
};
},
data () {
return {
editor: null,
id: uidGen()
};
},
watch: {
mode () {
if (this.editor)
this.editor.session.setMode(`ace/mode/${this.mode}`);
},
editorTheme () {
if (this.editor)
this.editor.setTheme(`ace/theme/${this.editorTheme}`);
},
editorFontSize () {
const sizes = {
small: '12px',
medium: '14px',
large: '16px'
};
if (this.editor) { watch(mode, () => {
this.editor.setOptions({ if (editor)
fontSize: sizes[this.editorFontSize] editor.session.setMode(`ace/mode/${props.mode}`);
}); });
}
}, watch(editorTheme, () => {
autoComplete () { if (editor)
if (this.editor) { editor.setTheme(`ace/theme/${editorTheme.value}`);
this.editor.setOptions({ });
enableLiveAutocompletion: this.autoComplete
}); watch(editorFontSize, () => {
} const sizes = {
}, small: 12,
lineWrap () { medium: 14,
if (this.editor) { large: 16
this.editor.setOptions({ };
wrap: this.lineWrap
}); if (editor) {
} editor.setOptions({
} fontSize: sizes[editorFontSize.value as undefined as 'small' | 'medium' | 'large']
},
mounted () {
this.editor = ace.edit(`editor-${this.id}`, {
mode: `ace/mode/${this.mode}`,
theme: `ace/theme/${this.editorTheme}`,
value: this.modelValue || '',
fontSize: '14px',
printMargin: false,
readOnly: this.readOnly,
showLineNumbers: this.showLineNumbers,
showGutter: this.showLineNumbers
}); });
}
});
this.editor.setOptions({ watch(autoComplete, () => {
enableBasicAutocompletion: false, if (editor) {
wrap: this.lineWrap, editor.setOptions({
enableSnippets: false, enableLiveAutocompletion: autoComplete.value
enableLiveAutocompletion: false
}); });
}
});
this.editor.session.on('change', () => { watch(lineWrap, () => {
const content = this.editor.getValue(); if (editor) {
this.$emit('update:modelValue', content); editor.setOptions({
wrap: lineWrap.value
}); });
}
});
if (this.autoFocus) { onMounted(() => {
setTimeout(() => { editor = ace.edit(`editor-${id}`, {
this.editor.focus(); mode: `ace/mode/${mode.value}`,
this.editor.resize(); theme: `ace/theme/${editorTheme.value}`,
}, 20); value: props.modelValue || '',
} fontSize: 14,
printMargin: false,
readOnly: props.readOnly,
showLineNumbers: props.showLineNumbers,
showGutter: props.showLineNumbers
});
editor.setOptions({
enableBasicAutocompletion: false,
wrap: lineWrap,
enableSnippets: false,
enableLiveAutocompletion: false
});
editor.session.on('changeFold', () => {
const content = editor.getValue();
emit('update:modelValue', content);
});
if (props.autoFocus) {
setTimeout(() => { setTimeout(() => {
this.editor.resize(); editor.focus();
editor.resize();
}, 20); }, 20);
} }
};
setTimeout(() => {
editor.resize();
}, 20);
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -9,67 +9,64 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { computed } from '@vue/reactivity';
name: 'BaseToast', import { ref, watch } from 'vue';
props: {
message: {
type: String,
default: ''
},
status: {
type: String,
default: ''
}
},
emits: ['close'],
data () {
return {
isVisible: false
};
},
computed: {
toastStatus () {
let className = '';
let iconName = '';
switch (this.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName }; const props = defineProps({
} message: {
type: String,
default: ''
}, },
watch: { status: {
message: function () { type: String,
if (this.message) default: ''
this.isVisible = true;
else
this.isVisible = false;
}
},
methods: {
hideToast () {
this.isVisible = false;
this.$emit('close');
}
} }
});
const isVisible = ref(false);
const message = ref(props.message);
const emit = defineEmits(['close']);
const toastStatus = computed(() => {
let className = '';
let iconName = '';
switch (props.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName };
});
watch(message, () => {
if (message.value)
isVisible.value = true;
else
isVisible.value = false;
});
const hideToast = () => {
isVisible.value = false;
emit('close');
}; };
</script> </script>
<style scoped> <style scoped>
.toast { .toast {
display: flex; display: flex;

View File

@ -7,7 +7,7 @@
{{ lastPart(modelValue) }} {{ lastPart(modelValue) }}
</span> </span>
<i <i
v-if="modelValue.length" v-if="modelValue"
class="file-uploader-reset mdi mdi-close" class="file-uploader-reset mdi mdi-close"
@click.prevent="clear" @click.prevent="clear"
/> />
@ -22,41 +22,35 @@
</label> </label>
</template> </template>
<script> <script setup lang="ts">
export default { import { uidGen } from 'common/libs/uidGen';
name: 'BaseUploadInput',
props: {
message: {
default: 'Browse',
type: String
},
modelValue: {
default: '',
type: String
}
},
emits: ['change', 'clear'],
data () {
return {
id: null
};
},
mounted () {
this.id = this._uid;
},
methods: {
clear () {
this.$emit('clear');
},
lastPart (string) {
if (!string) return '';
string = string.split(/[/\\]+/).pop(); defineProps({
if (string.length >= 19) message: {
string = `...${string.slice(-19)}`; default: 'Browse',
return string; type: String
} },
modelValue: {
default: '',
type: String
} }
});
const emit = defineEmits(['change', 'clear']);
const id = uidGen();
const clear = () => {
emit('clear');
};
const lastPart = (string: string) => {
if (!string) return '';
string = string.split(/[/\\]+/).pop();
if (string.length >= 19)
string = `...${string.slice(-19)}`;
return string;
}; };
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="vscroll-holder"> <div ref="root" class="vscroll-holder">
<div <div
class="vscroll-spacer" class="vscroll-spacer"
:style="{ :style="{
@ -20,71 +20,77 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
name: 'BaseVirtualScroll',
props: {
items: Array,
itemHeight: Number,
visibleHeight: Number,
scrollElement: {
type: HTMLDivElement,
default: null
}
},
data () {
return {
topHeight: 0,
bottomHeight: 0,
visibleItems: [],
renderTimeout: null,
localScrollElement: null
};
},
watch: {
scrollElement () {
this.setScrollElement();
}
},
mounted () {
this.setScrollElement();
},
beforeUnmount () {
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
},
methods: {
checkScrollPosition (e) {
clearTimeout(this.renderTimeout);
this.renderTimeout = setTimeout(() => { const props = defineProps({
this.updateWindow(e); items: Array,
}, 200); itemHeight: Number,
}, visibleHeight: Number,
updateWindow () { scrollElement: {
const visibleItemsCount = Math.ceil(this.visibleHeight / this.itemHeight); type: HTMLDivElement,
const totalScrollHeight = this.items.length * this.itemHeight; default: null
const offset = 50;
const scrollTop = this.localScrollElement.scrollTop;
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
const lastCutIndex = lastVisibleIndex + offset;
this.visibleItems = this.items.slice(firstCutIndex, lastCutIndex);
this.topHeight = firstCutIndex * this.itemHeight;
this.bottomHeight = totalScrollHeight - this.visibleItems.length * this.itemHeight - this.topHeight;
},
setScrollElement () {
if (this.localScrollElement)
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
this.localScrollElement = this.scrollElement ? this.scrollElement : this.$el;
this.updateWindow();
this.localScrollElement.addEventListener('scroll', this.checkScrollPosition);
}
} }
});
const root = ref(null);
const topHeight: Ref<number> = ref(0);
const bottomHeight: Ref<number> = ref(0);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const visibleItems: Ref<any[]> = ref([]);
const renderTimeout: Ref<NodeJS.Timeout> = ref(null);
const localScrollElement: Ref<HTMLDivElement> = ref(null);
const scrollElement = ref(props.scrollElement);
const checkScrollPosition = () => {
clearTimeout(renderTimeout.value);
renderTimeout.value = setTimeout(() => {
updateWindow();
}, 200);
}; };
const updateWindow = () => {
const visibleItemsCount = Math.ceil(props.visibleHeight / props.itemHeight);
const totalScrollHeight = props.items.length * props.itemHeight;
const offset = 50;
const scrollTop = localScrollElement.value.scrollTop;
const firstVisibleIndex = Math.floor(scrollTop / props.itemHeight);
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
const lastCutIndex = lastVisibleIndex + offset;
visibleItems.value = props.items.slice(firstCutIndex, lastCutIndex);
topHeight.value = firstCutIndex * props.itemHeight;
bottomHeight.value = totalScrollHeight - visibleItems.value.length * props.itemHeight - topHeight.value;
};
const setScrollElement = () => {
if (localScrollElement.value)
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
localScrollElement.value = scrollElement.value ? scrollElement.value : root.value;
updateWindow();
localScrollElement.value.addEventListener('scroll', checkScrollPosition);
};
watch(scrollElement, () => {
setScrollElement();
});
onMounted(() => {
setScrollElement();
});
onBeforeUnmount(() => {
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
});
defineExpose({
updateWindow
});
</script> </script>

View File

@ -52,7 +52,7 @@
> >
<BaseUploadInput <BaseUploadInput
v-else-if="inputProps().type === 'file'" v-else-if="inputProps().type === 'file'"
:value="selectedValue" :model-value="selectedValue"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="clearValue" @clear="clearValue"
@change="filesChange($event)" @change="filesChange($event)"

View File

@ -134,7 +134,7 @@
</template> </template>
<script> <script>
import arrayToFile from '../libs/arrayToFile'; import { arrayToFile } from '../libs/arrayToFile';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';

View File

@ -328,7 +328,7 @@ import { storeToRefs } from 'pinia';
import { useApplicationStore } from '@/stores/application'; import { useApplicationStore } from '@/stores/application';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import localesNames from '@/i18n/supported-locales'; import { localesNames } from '@/i18n/supported-locales';
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate'; import ModalSettingsUpdate from '@/components/ModalSettingsUpdate';
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog'; import ModalSettingsChangelog from '@/components/ModalSettingsChangelog';
import BaseTextEditor from '@/components/BaseTextEditor'; import BaseTextEditor from '@/components/BaseTextEditor';

View File

@ -96,7 +96,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="connection.databasePath" :model-value="connection.databasePath"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('databasePath')" @clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')" @change="pathSelection($event, 'databasePath')"
@ -211,7 +211,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="connection.key" :model-value="connection.key"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('key')" @clear="pathClear('key')"
@change="pathSelection($event, 'key')" @change="pathSelection($event, 'key')"
@ -224,7 +224,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="connection.cert" :model-value="connection.cert"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('cert')" @clear="pathClear('cert')"
@change="pathSelection($event, 'cert')" @change="pathSelection($event, 'cert')"
@ -237,7 +237,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="connection.ca" :model-value="connection.ca"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('ca')" @clear="pathClear('ca')"
@change="pathSelection($event, 'ca')" @change="pathSelection($event, 'ca')"
@ -342,7 +342,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="connection.sshKey" :model-value="connection.sshKey"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('sshKey')" @clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')" @change="pathSelection($event, 'sshKey')"

View File

@ -92,7 +92,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="localConnection.databasePath" :model-value="localConnection.databasePath"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('databasePath')" @clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')" @change="pathSelection($event, 'databasePath')"
@ -207,7 +207,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="localConnection.key" :model-value="localConnection.key"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('key')" @clear="pathClear('key')"
@change="pathSelection($event, 'key')" @change="pathSelection($event, 'key')"
@ -220,7 +220,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="localConnection.cert" :model-value="localConnection.cert"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('cert')" @clear="pathClear('cert')"
@change="pathSelection($event, 'cert')" @change="pathSelection($event, 'cert')"
@ -233,7 +233,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="localConnection.ca" :model-value="localConnection.ca"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('ca')" @clear="pathClear('ca')"
@change="pathSelection($event, 'ca')" @change="pathSelection($event, 'ca')"
@ -330,7 +330,7 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<BaseUploadInput <BaseUploadInput
:value="localConnection.sshKey" :model-value="localConnection.sshKey"
:message="$t('word.browse')" :message="$t('word.browse')"
@clear="pathClear('sshKey')" @clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')" @change="pathSelection($event, 'sshKey')"

View File

@ -90,7 +90,7 @@
</label> </label>
</div> </div>
<div class="td p-0 type-int" tabindex="0"> <div class="td p-0 type-int" tabindex="0">
<template v-if="fieldType.length"> <template v-if="fieldType?.length">
<span <span
v-if="!isInlineEditor.length" v-if="!isInlineEditor.length"
class="cell-content" class="cell-content"
@ -355,7 +355,7 @@ export default {
}, },
props: { props: {
row: Object, row: Object,
dataTypes: Array, dataTypes: { type: Array, default: () => [] },
indexes: Array, indexes: Array,
foreigns: Array, foreigns: Array,
customizations: Object customizations: Object

View File

@ -112,7 +112,7 @@ import { uidGen } from 'common/libs/uidGen';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import arrayToFile from '../libs/arrayToFile'; import { arrayToFile } from '../libs/arrayToFile';
import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes'; import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes';
import BaseVirtualScroll from '@/components/BaseVirtualScroll'; import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import WorkspaceTabQueryTableRow from '@/components/WorkspaceTabQueryTableRow'; import WorkspaceTabQueryTableRow from '@/components/WorkspaceTabQueryTableRow';

View File

@ -1,4 +1,4 @@
export default { export const localesNames = {
'en-US': 'English', 'en-US': 'English',
'it-IT': 'Italiano', 'it-IT': 'Italiano',
'ar-SA': 'العربية', 'ar-SA': 'العربية',

View File

@ -29,7 +29,7 @@ createApp(App)
.mount('#app'); .mount('#app');
const { locale } = useSettingsStore(); const { locale } = useSettingsStore();
i18n.global.locale = locale; i18n.global.locale = locale as string;// TODO: temp
// IPC exceptions // IPC exceptions
ipcRenderer.on('unhandled-exception', (event, error) => { ipcRenderer.on('unhandled-exception', (event, error) => {
@ -59,7 +59,7 @@ ipcRenderer.on('no-auto-update', () => {
ipcRenderer.on('download-progress', (event, data) => { ipcRenderer.on('download-progress', (event, data) => {
useApplicationStore().updateStatus = 'downloading'; useApplicationStore().updateStatus = 'downloading';
useApplicationStore().downloadprogress = data.percent; useApplicationStore().downloadProgress = data.percent;
}); });
ipcRenderer.on('update-downloaded', () => { ipcRenderer.on('update-downloaded', () => {

View File

@ -4,6 +4,9 @@ import Connection from '@/ipc-api/Connection';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import Users from '@/ipc-api/Users'; import Users from '@/ipc-api/Users';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import customizations from 'common/customizations';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
@ -22,14 +25,14 @@ export const useWorkspacesStore = defineStore('workspaces', {
if (state.selectedWorkspace) return state.selectedWorkspace; if (state.selectedWorkspace) return state.selectedWorkspace;
return state.workspaces[0].uid; return state.workspaces[0].uid;
}, },
getWorkspace: state => uid => { getWorkspace: state => (uid) => {
return state.workspaces.find(workspace => workspace.uid === uid); return state.workspaces.find(workspace => workspace.uid === uid);
}, },
getDatabaseVariable: state => (uid, name) => { getDatabaseVariable: state => (uid, name) => {
return state.workspaces.find(workspace => workspace.uid === uid).variables.find(variable => variable.name === name); return state.workspaces.find(workspace => workspace.uid === uid).variables.find(variable => variable.name === name);
}, },
getWorkspaceTab (state) { getWorkspaceTab (state) {
return tUid => { return (tUid) => {
if (!this.getSelected) return; if (!this.getSelected) return;
const workspace = state.workspaces.find(workspace => workspace.uid === this.getSelected); const workspace = state.workspaces.find(workspace => workspace.uid === this.getSelected);
if ('tabs' in workspace) if ('tabs' in workspace)
@ -89,24 +92,24 @@ export const useWorkspacesStore = defineStore('workspaces', {
else { else {
let dataTypes = []; let dataTypes = [];
let indexTypes = []; let indexTypes = [];
let customizations = {}; let clientCustomizations;
switch (connection.client) { switch (connection.client) {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
dataTypes = require('common/data-types/mysql'); dataTypes = require('common/data-types/mysql').default;
indexTypes = require('common/index-types/mysql'); indexTypes = require('common/index-types/mysql').default;
customizations = require('common/customizations/mysql'); clientCustomizations = customizations.mysql;
break; break;
case 'pg': case 'pg':
dataTypes = require('common/data-types/postgresql'); dataTypes = require('common/data-types/postgresql').default;
indexTypes = require('common/index-types/postgresql'); indexTypes = require('common/index-types/postgresql').default;
customizations = require('common/customizations/postgresql'); clientCustomizations = customizations.pg;
break; break;
case 'sqlite': case 'sqlite':
dataTypes = require('common/data-types/sqlite'); dataTypes = require('common/data-types/sqlite').default;
indexTypes = require('common/index-types/sqlite'); indexTypes = require('common/index-types/sqlite').default;
customizations = require('common/customizations/sqlite'); clientCustomizations = customizations.sqlite;
break; break;
} }
@ -144,7 +147,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
client: connection.client, client: connection.client,
dataTypes, dataTypes,
indexTypes, indexTypes,
customizations, customizations: clientCustomizations,
structure: response, structure: response,
connectionStatus: 'connected', connectionStatus: 'connected',
tabs: cachedTabs, tabs: cachedTabs,

2
src/renderer/untyped.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
declare module '@/App.vue';
declare module 'v-mask';