mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
feat(UI): folders implementation
This commit is contained in:
@ -8,28 +8,20 @@
|
|||||||
:swap-threshold="0.3"
|
:swap-threshold="0.3"
|
||||||
@start="emit('start', $event)"
|
@start="emit('start', $event)"
|
||||||
@end="emit('end', $event)"
|
@end="emit('end', $event)"
|
||||||
@move="emit('move', $event)"
|
|
||||||
@change="emit('update:modelValue', localList)"
|
@change="emit('update:modelValue', localList)"
|
||||||
>
|
>
|
||||||
<template #item="{ element }">
|
<template #item="{ element }">
|
||||||
<li
|
<li
|
||||||
v-if="element.isFolder || !folderedConnections.includes(element.uid)"
|
v-if="element.isFolder || !folderedConnections.includes(element.uid)"
|
||||||
:draggable="true"
|
:draggable="true"
|
||||||
class="settingbar-element btn btn-link"
|
|
||||||
:class="{ 'selected': element.uid === selectedWorkspace }"
|
|
||||||
@dragstart="draggedElement = element.uid"
|
@dragstart="draggedElement = element.uid"
|
||||||
@dragend="coveredElement = false"
|
@dragend="dragEnd"
|
||||||
@contextmenu.prevent="emit('context', $event, element)"
|
@contextmenu.prevent="emit('context', $event, element)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!element.isFolder && !folderedConnections.includes(element.uid)"
|
v-if="!element.isFolder && !folderedConnections.includes(element.uid)"
|
||||||
class="p-relative"
|
class="settingbar-element btn btn-link"
|
||||||
:style="`
|
:class="{ 'selected': element.uid === selectedWorkspace }"
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
`"
|
|
||||||
:title="getConnectionName(element.uid)"
|
:title="getConnectionName(element.uid)"
|
||||||
@click.stop="selectWorkspace(element.uid)"
|
@click.stop="selectWorkspace(element.uid)"
|
||||||
>
|
>
|
||||||
@ -43,25 +35,25 @@
|
|||||||
@dragleave="coveredElement = false"
|
@dragleave="coveredElement = false"
|
||||||
@change="createFolder"
|
@change="createFolder"
|
||||||
/>
|
/>
|
||||||
<i
|
<i v-if="coveredElement === element.uid && draggedElement !== coveredElement" class="settingbar-element-icon mdi mdi-folder-plus mdi-36px" />
|
||||||
class="settingbar-element-icon dbi"
|
<template v-else>
|
||||||
:class="[`dbi-${element.client}`, getStatusBadge(element.uid)]"
|
<i
|
||||||
/>
|
class="settingbar-element-icon dbi"
|
||||||
<small class="settingbar-element-name">{{ getConnectionName(element.uid) }}</small>
|
:class="[`dbi-${element.client}`, getStatusBadge(element.uid)]"
|
||||||
|
/>
|
||||||
|
<small class="settingbar-element-name">{{ getConnectionName(element.uid) }}</small>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<SettingBarConnectionsFolder
|
||||||
v-else-if="element.isFolder"
|
v-else-if="element.isFolder"
|
||||||
class="p-relative"
|
:folder="element"
|
||||||
:style="`
|
:covered-element="coveredElement"
|
||||||
width: 100%;
|
:dragged-element="draggedElement"
|
||||||
height: 100%;
|
@select-workspace="selectWorkspace"
|
||||||
display: flex;
|
@covered="coveredElement = element.uid"
|
||||||
align-items: center;
|
@uncovered="coveredElement = false"
|
||||||
`"
|
@folder-sort="emit('update:modelValue', localList)"
|
||||||
>
|
/>
|
||||||
<i class="settingbar-element-icon mdi mdi-folder mdi-36px" />
|
|
||||||
<small class="settingbar-element-name">{{ element.name }}</small>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</Draggable>
|
</Draggable>
|
||||||
@ -72,6 +64,7 @@ import { storeToRefs } from 'pinia';
|
|||||||
import * as Draggable from 'vuedraggable';
|
import * as Draggable from 'vuedraggable';
|
||||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
import SettingBarConnectionsFolder from '@/components/SettingBarConnectionsFolder.vue';
|
||||||
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
const workspacesStore = useWorkspacesStore();
|
||||||
const connectionsStore = useConnectionsStore();
|
const connectionsStore = useConnectionsStore();
|
||||||
@ -107,9 +100,13 @@ const folderedConnections = computed(() => {
|
|||||||
}, []);
|
}, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dragEnd = () => {
|
||||||
|
coveredElement.value = false;
|
||||||
|
draggedElement.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
const createFolder = ({ added }: {added: { element: SidebarElement }}) => {
|
const createFolder = ({ added }: {added: { element: SidebarElement }}) => {
|
||||||
if (typeof coveredElement.value === 'string' && !added.element.isFolder) {
|
if (typeof coveredElement.value === 'string' && !added.element.isFolder) {
|
||||||
console.log('added', added.element);
|
|
||||||
// Create folder
|
// Create folder
|
||||||
addFolder({
|
addFolder({
|
||||||
after: coveredElement.value,
|
after: coveredElement.value,
|
||||||
@ -146,7 +143,7 @@ watch(() => props.modelValue, (value) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style lang="scss">
|
||||||
.drag-area {
|
.drag-area {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
@ -158,16 +155,20 @@ watch(() => props.modelValue, (value) => {
|
|||||||
transition: all .2s;
|
transition: all .2s;
|
||||||
|
|
||||||
&.folder-preview {
|
&.folder-preview {
|
||||||
border: 1px dashed;
|
border: 2px dotted;
|
||||||
border-radius: 5px;
|
border-radius: 15px;
|
||||||
left: 5px;
|
left: 5px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: none!important;
|
display: none!important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ghost {
|
||||||
|
background-color: rgba($primary-color, 20%);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
240
src/renderer/components/SettingBarConnectionsFolder.vue
Normal file
240
src/renderer/components/SettingBarConnectionsFolder.vue
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="settingbar-element btn btn-link p-1"
|
||||||
|
:style="isOpen ? `height: auto; opacity: 1;` : null"
|
||||||
|
:title="folder.name"
|
||||||
|
>
|
||||||
|
<Draggable
|
||||||
|
class="folder-container"
|
||||||
|
:item-key="((item: string) => localList.indexOf(item))"
|
||||||
|
:class="[{'opened': isOpen}]"
|
||||||
|
:style="[`background: ${folder.color};`, isOpen ? `max-height: ${60*(folder.connections.length+1)}px` : 'max-height: 60px']"
|
||||||
|
:list="localList"
|
||||||
|
ghost-class="ghost"
|
||||||
|
:group="{ name: 'connections' }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div
|
||||||
|
v-if="!isOpen"
|
||||||
|
class="folder-overlay"
|
||||||
|
@click="isOpen = true"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="isOpen"
|
||||||
|
class="folder-icon"
|
||||||
|
:style="`color: ${folder.color};`"
|
||||||
|
@click="isOpen = false"
|
||||||
|
>
|
||||||
|
<i class="folder-icon-open mdi mdi-folder-open mdi-36px" />
|
||||||
|
<i class="folder-icon-close mdi mdi-folder mdi-36px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #item="{ element }">
|
||||||
|
<div
|
||||||
|
:key="element"
|
||||||
|
class="folder-element"
|
||||||
|
|
||||||
|
:class="{ 'selected': element === selectedWorkspace }"
|
||||||
|
@click="emit('select-workspace', element)"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="folder-element-icon dbi"
|
||||||
|
:class="[`dbi-${getConnectionByUid(element).client}`, getStatusBadge(getConnectionByUid(element).uid)]"
|
||||||
|
/>
|
||||||
|
<small v-if="isOpen" class="folder-element-name">{{ getConnectionName(element) }}</small>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Draggable>
|
||||||
|
<SettingBarConnections
|
||||||
|
v-if="draggedElement && !foldersUid.includes(draggedElement)"
|
||||||
|
class="drag-area"
|
||||||
|
:class="[{'folder-preview': coveredElement === folder.uid && draggedElement !== coveredElement}]"
|
||||||
|
:list="dummyNested"
|
||||||
|
@dragenter="emit('covered')"
|
||||||
|
@dragleave="emit('uncovered')"
|
||||||
|
@change="addIntoFolder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, PropType, ref, watch } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import * as Draggable from 'vuedraggable';
|
||||||
|
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||||
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
import SettingBarConnections from '@/components/SettingBarConnections.vue';
|
||||||
|
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
const connectionsStore = useConnectionsStore();
|
||||||
|
|
||||||
|
const { getFolders: folders } = storeToRefs(connectionsStore);
|
||||||
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
|
|
||||||
|
const { getWorkspace } = workspacesStore;
|
||||||
|
const { getConnectionByUid, getConnectionName, addToFolder } = connectionsStore;
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
folder: {
|
||||||
|
type: Object as PropType<SidebarElement>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
draggedElement: {
|
||||||
|
type: [String, Boolean] as PropType<string | false>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
coveredElement: {
|
||||||
|
type: [String, Boolean] as PropType<string | false>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['covered', 'uncovered', 'select-workspace', 'folder-sort']);
|
||||||
|
|
||||||
|
const isOpen = ref(false);
|
||||||
|
const localList = ref(props.folder.connections);
|
||||||
|
const dummyNested = ref([]);
|
||||||
|
|
||||||
|
const foldersUid = computed(() => folders.value.reduce<string[]>((acc, curr) => {
|
||||||
|
acc.push(curr.uid);
|
||||||
|
return acc;
|
||||||
|
}, []));
|
||||||
|
|
||||||
|
const addIntoFolder = ({ added }: {added: { element: SidebarElement }}) => {
|
||||||
|
if (typeof props.coveredElement === 'string' && !added.element.isFolder) {
|
||||||
|
addToFolder({
|
||||||
|
folder: props.coveredElement,
|
||||||
|
connection: added.element.uid
|
||||||
|
});
|
||||||
|
|
||||||
|
emit('uncovered');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusBadge = (uid: string) => {
|
||||||
|
if (getWorkspace(uid)) {
|
||||||
|
const status = getWorkspace(uid).connectionStatus;
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'connected':
|
||||||
|
return 'badge badge-connected';
|
||||||
|
case 'connecting':
|
||||||
|
return 'badge badge-connecting';
|
||||||
|
case 'failed':
|
||||||
|
return 'badge badge-failed';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => dummyNested.value.length, () => {
|
||||||
|
dummyNested.value = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(localList, () => {
|
||||||
|
emit('folder-sort');
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.folder-container{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
grid-template-rows: 50%;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: background .3s;
|
||||||
|
|
||||||
|
&.opened {
|
||||||
|
gap: 4px 6px;
|
||||||
|
grid-template-columns: auto;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
background: rgba($color: #fff, $alpha: 0.1)!important;
|
||||||
|
transition: max-height .3s;
|
||||||
|
|
||||||
|
.folder-element {
|
||||||
|
opacity: .6;
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-icon {
|
||||||
|
height: 2.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
position: relative;
|
||||||
|
transition: opacity .2s;
|
||||||
|
|
||||||
|
.folder-icon-open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-icon-close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
.folder-icon-open {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-icon-close {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-element {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
position: relative;
|
||||||
|
transition: opacity .2s;
|
||||||
|
|
||||||
|
&:hover, &.selected {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-element-name {
|
||||||
|
position: absolute;
|
||||||
|
max-width: 90%;
|
||||||
|
bottom: 0;
|
||||||
|
font-size: 65%;
|
||||||
|
font-style: normal;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.opened){
|
||||||
|
.folder-element {
|
||||||
|
|
||||||
|
.folder-element-icon {
|
||||||
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -28,7 +28,7 @@
|
|||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ t(contextConnection.isFolder ? 'message.deleteFolder' : 'message.deleteConnection') }}
|
<i class="mdi mdi-24px mr-1" :class="[contextConnection.isFolder ? 'mdi-folder-remove' : 'mdi-server-remove']" /> {{ t(contextConnection.isFolder ? 'message.deleteFolder' : 'message.deleteConnection') }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
|
@ -43,6 +43,14 @@
|
|||||||
animation: jump-down-in 0.2s reverse;
|
animation: jump-down-in 0.2s reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flip-list-move {
|
||||||
|
transition: transform 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-move {
|
||||||
|
transition: transform 0s;
|
||||||
|
}
|
||||||
|
|
||||||
.pulse {
|
.pulse {
|
||||||
animation-name: pulse;
|
animation-name: pulse;
|
||||||
animation-duration: 2s;
|
animation-duration: 2s;
|
||||||
|
@ -90,17 +90,25 @@ export const useConnectionsStore = defineStore('connections', {
|
|||||||
isFolder: true,
|
isFolder: true,
|
||||||
uid: uidGen('F'),
|
uid: uidGen('F'),
|
||||||
name: '',
|
name: '',
|
||||||
color: 'orange',
|
color: '#e36929',
|
||||||
connections: params.connections
|
connections: params.connections
|
||||||
});
|
});
|
||||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||||
},
|
},
|
||||||
|
addToFolder (params: {folder: string; connection: string}) {
|
||||||
|
this.connectionsOrder = this.connectionsOrder.map((conn: SidebarElement) => {
|
||||||
|
if (conn.uid === params.folder)
|
||||||
|
conn.connections.push(params.connection);
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
});
|
||||||
|
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||||
|
},
|
||||||
deleteConnection (connection: SidebarElement | ConnectionParams) {
|
deleteConnection (connection: SidebarElement | ConnectionParams) {
|
||||||
this.connections = (this.connections as SidebarElement[]).filter(el => el.uid !== connection.uid);
|
this.connections = (this.connections as SidebarElement[]).filter(el => el.uid !== connection.uid);
|
||||||
persistentStore.set('connections', this.connections);
|
persistentStore.set('connections', this.connections);
|
||||||
|
|
||||||
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => el.uid !== connection.uid);
|
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => el.uid !== connection.uid);
|
||||||
console.log(connection.uid, this.connectionsOrder);
|
|
||||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||||
},
|
},
|
||||||
editConnection (connection: ConnectionParams) {
|
editConnection (connection: ConnectionParams) {
|
||||||
@ -117,6 +125,29 @@ export const useConnectionsStore = defineStore('connections', {
|
|||||||
persistentStore.set('connections', this.connections);
|
persistentStore.set('connections', this.connections);
|
||||||
},
|
},
|
||||||
updateConnectionsOrder (connections: SidebarElement[]) {
|
updateConnectionsOrder (connections: SidebarElement[]) {
|
||||||
|
const invalidElements = connections.reduce<{index: number; uid: string}[]>((acc, curr, i) => {
|
||||||
|
if (typeof curr === 'string')
|
||||||
|
acc.push({ index: i, uid: curr });
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!invalidElements.length) return;
|
||||||
|
|
||||||
|
invalidElements.forEach(el => {
|
||||||
|
const connIndex = connections.findIndex(conn => conn.uid === el.uid);
|
||||||
|
const conn = connections[connIndex];
|
||||||
|
|
||||||
|
if (connIndex === -1) return;
|
||||||
|
|
||||||
|
connections.splice(connIndex, 1);// Delete olt object
|
||||||
|
connections.splice(el.index, 1, { // Move to new position
|
||||||
|
isFolder: false,
|
||||||
|
client: conn.client,
|
||||||
|
uid: conn.uid
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.connectionsOrder = connections;
|
this.connectionsOrder = connections;
|
||||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user