mirror of
				https://github.com/Fabio286/antares.git
				synced 2025-06-05 21:59:22 +02:00 
			
		
		
		
	feat: custom SVG icons for connections, closes #663
This commit is contained in:
		| @@ -1,11 +1,19 @@ | ||||
| <template> | ||||
|    <SvgIcon | ||||
|       v-if="type === 'mdi'" | ||||
|       :type="type" | ||||
|       :path="iconPath" | ||||
|       :size="size" | ||||
|       :rotate="rotate" | ||||
|       :class="iconFlip" | ||||
|    /> | ||||
|    <svg | ||||
|       v-else | ||||
|       :width="size" | ||||
|       :height="size" | ||||
|       :viewBox="`0 0 ${size} ${size}`" | ||||
|       v-html="iconPath" | ||||
|    /> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| @@ -13,6 +21,10 @@ import SvgIcon from '@jamescoyle/vue-icon'; | ||||
| import * as Icons from '@mdi/js'; | ||||
| import { computed, PropType } from 'vue'; | ||||
|  | ||||
| import { useConnectionsStore } from '@/stores/connections'; | ||||
|  | ||||
| const { getIconByUid } = useConnectionsStore(); | ||||
|  | ||||
| const props = defineProps({ | ||||
|    iconName: { | ||||
|       type: String, | ||||
| @@ -23,7 +35,7 @@ const props = defineProps({ | ||||
|       default: 48 | ||||
|    }, | ||||
|    type: { | ||||
|       type: String, | ||||
|       type: String as PropType<'mdi' | 'custom'>, | ||||
|       default: () => 'mdi' | ||||
|    }, | ||||
|    flip: { | ||||
| @@ -37,7 +49,18 @@ const props = defineProps({ | ||||
| }); | ||||
|  | ||||
| const iconPath = computed(() => { | ||||
|    return (Icons as {[k:string]: string})[props.iconName]; | ||||
|    if (props.type === 'mdi') | ||||
|       return (Icons as {[k:string]: string})[props.iconName]; | ||||
|    else if (props.type === 'custom') { | ||||
|       const base64 = getIconByUid(props.iconName)?.base64; | ||||
|       const svgString = Buffer | ||||
|          .from(base64, 'base64') | ||||
|          .toString('utf-8') | ||||
|          .replaceAll(/width="[^"]*"|height="[^"]*"/g, ''); | ||||
|  | ||||
|       return svgString; | ||||
|    } | ||||
|    return null; | ||||
| }); | ||||
|  | ||||
| const iconFlip = computed(() => { | ||||
|   | ||||
| @@ -64,6 +64,7 @@ | ||||
|                                  > | ||||
|                                     <BaseIcon | ||||
|                                        :icon-name="camelize(connection.icon)" | ||||
|                                        :type="connection.hasCustomIcon ? 'custom' : 'mdi'" | ||||
|                                        :size="42" | ||||
|                                     /> | ||||
|                                  </div> | ||||
| @@ -278,12 +279,15 @@ const remappedConnections = computed(() => { | ||||
|       .map(c => { | ||||
|          const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0; | ||||
|          const connIcon = connectionsOrder.value.find((co) => co.uid === c.uid).icon; | ||||
|          const connHasCustomIcon = connectionsOrder.value.find((co) => co.uid === c.uid).hasCustomIcon; | ||||
|          const folder = folders.value.find(f => f.connections.includes(c.uid)); | ||||
|  | ||||
|          return { | ||||
|             ...c, | ||||
|             icon: connIcon, | ||||
|             color: folder?.color, | ||||
|             folderName: folder?.name, | ||||
|             hasCustomIcon: connHasCustomIcon, | ||||
|             time: connTime | ||||
|          }; | ||||
|       }) | ||||
|   | ||||
| @@ -49,18 +49,46 @@ | ||||
|                                  class="icon-box" | ||||
|                                  :title="icon.name" | ||||
|                                  :class="[{'selected': localConnection.icon === icon.code}]" | ||||
|                                  @click="localConnection.icon = icon.code" | ||||
|                                  @click="setIcon(icon.code)" | ||||
|                               /> | ||||
|                               <div | ||||
|                                  v-else | ||||
|                                  class="icon-box" | ||||
|                                  :title="icon.name" | ||||
|                                  :class="[`dbi dbi-${connection.client}`, {'selected': localConnection.icon === icon.code}]" | ||||
|                                  @click="localConnection.icon = icon.code" | ||||
|                                  :class="[`dbi dbi-${connection.client}`, {'selected': localConnection.icon === null}]" | ||||
|                                  @click="setIcon(null)" | ||||
|                               /> | ||||
|                            </div> | ||||
|                         </div> | ||||
|                      </div> | ||||
|                      <div class="form-group"> | ||||
|                         <div class="col-3"> | ||||
|                            <label class="form-label">{{ t('application.customIcon') }}</label> | ||||
|                         </div> | ||||
|                         <div class="col-9 icons-wrapper"> | ||||
|                            <div | ||||
|                               v-for="icon in customIcons" | ||||
|                               :key="icon.uid" | ||||
|                            > | ||||
|                               <BaseIcon | ||||
|                                  v-if="icon.uid" | ||||
|                                  :icon-name="icon.uid" | ||||
|                                  type="custom" | ||||
|                                  :size="36" | ||||
|                                  class="icon-box" | ||||
|                                  :class="[{'selected': localConnection.icon === icon.uid}]" | ||||
|                                  @click="setIcon(icon.uid, 'custom')" | ||||
|                                  @contextmenu.prevent="contextMenu($event, icon.uid)" | ||||
|                               /> | ||||
|                            </div> | ||||
|                            <BaseIcon | ||||
|                               :icon-name="'mdiPlus'" | ||||
|                               :size="36" | ||||
|                               class="icon-box" | ||||
|                               @click="openFile" | ||||
|                            /> | ||||
|                         </div> | ||||
|                      </div> | ||||
|                   </form> | ||||
|                </div> | ||||
|             </div> | ||||
| @@ -74,20 +102,45 @@ | ||||
|             </div> | ||||
|          </div> | ||||
|       </div> | ||||
|       <BaseContextMenu | ||||
|          v-if="isContext" | ||||
|          :context-event="contextEvent" | ||||
|          @close-context="isContext = false" | ||||
|       > | ||||
|          <div class="context-element" @click="removeIconHandler"> | ||||
|             <span class="d-flex"> | ||||
|                <BaseIcon | ||||
|                   class="text-light mt-1 mr-1" | ||||
|                   icon-name="mdiDelete" | ||||
|                   :size="18" | ||||
|                /> {{ t('general.delete') }}</span> | ||||
|          </div> | ||||
|       </BaseContextMenu> | ||||
|    </Teleport> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { storeToRefs } from 'pinia'; | ||||
| import { onBeforeUnmount, PropType, Ref, ref } from 'vue'; | ||||
| import { useI18n } from 'vue-i18n'; | ||||
|  | ||||
| import BaseContextMenu from '@/components/BaseContextMenu.vue'; | ||||
| import BaseIcon from '@/components/BaseIcon.vue'; | ||||
| import { useFocusTrap } from '@/composables/useFocusTrap'; | ||||
| import Application from '@/ipc-api/Application'; | ||||
| import { camelize } from '@/libs/camelize'; | ||||
| import { unproxify } from '@/libs/unproxify'; | ||||
| import { SidebarElement, useConnectionsStore } from '@/stores/connections'; | ||||
|  | ||||
| const connectionsStore = useConnectionsStore(); | ||||
|  | ||||
| const { addIcon, removeIcon, updateConnectionOrder, getConnectionName } = connectionsStore; | ||||
| const { customIcons } = storeToRefs(connectionsStore); | ||||
|  | ||||
| const isContext = ref(false); | ||||
| const contextContent: Ref<string> = ref(null); | ||||
| const contextEvent: Ref<MouseEvent> = ref(null); | ||||
|  | ||||
| const { t } = useI18n(); | ||||
|  | ||||
| const props = defineProps({ | ||||
| @@ -99,8 +152,6 @@ const props = defineProps({ | ||||
|  | ||||
| const emit = defineEmits(['close']); | ||||
|  | ||||
| const { updateConnectionOrder, getConnectionName } = connectionsStore; | ||||
|  | ||||
| const icons = [ | ||||
|    { name: 'default', code: null }, | ||||
|  | ||||
| @@ -160,14 +211,33 @@ const editFolderAppearance = () => { | ||||
|    closeModal(); | ||||
| }; | ||||
|  | ||||
| const camelize = (text: string) => { | ||||
|    const textArr = text.split('-'); | ||||
|    for (let i = 0; i < textArr.length; i++) { | ||||
|       if (i === 0) continue; | ||||
|       textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1); | ||||
|    } | ||||
| const setIcon = (code: string, type?: 'mdi' | 'custom') => { | ||||
|    localConnection.value.icon = code; | ||||
|    localConnection.value.hasCustomIcon = type === 'custom'; | ||||
| }; | ||||
|  | ||||
|    return textArr.join(''); | ||||
| const removeIconHandler = () => { | ||||
|    if (localConnection.value.icon === contextContent.value) { | ||||
|       setIcon(null); | ||||
|       updateConnectionOrder(localConnection.value); | ||||
|    } | ||||
|    removeIcon(contextContent.value); | ||||
|    isContext.value = false; | ||||
| }; | ||||
|  | ||||
| const openFile = async () => { | ||||
|    const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: '"SVG"', extensions: ['svg'] }] }); | ||||
|    if (result && !result.canceled) { | ||||
|       const file = result.filePaths[0]; | ||||
|       const content = await Application.readFile({ filePath: file, encoding: 'base64url' }); | ||||
|       addIcon(content); | ||||
|    } | ||||
| }; | ||||
|  | ||||
| const contextMenu = (event: MouseEvent, iconUid: string) => { | ||||
|    contextEvent.value = event; | ||||
|    contextContent.value = iconUid; | ||||
|    isContext.value = true; | ||||
| }; | ||||
|  | ||||
| const closeModal = () => emit('close'); | ||||
|   | ||||
| @@ -169,7 +169,7 @@ const emit = defineEmits(['close']); | ||||
| const { trapRef } = useFocusTrap(); | ||||
|  | ||||
| const { getConnectionName } = useConnectionsStore(); | ||||
| const { connectionsOrder, connections } = storeToRefs(useConnectionsStore()); | ||||
| const { connectionsOrder, connections, customIcons } = storeToRefs(useConnectionsStore()); | ||||
| const localConnections = unproxify<ConnectionParams[]>(connections.value); | ||||
| const localConnectionsOrder = unproxify<SidebarElement[]>(connectionsOrder.value); | ||||
|  | ||||
| @@ -246,7 +246,8 @@ const exportData = () => { | ||||
|  | ||||
|       const exportObj = encrypt(JSON.stringify({ | ||||
|          connections: filteredConnections, | ||||
|          connectionsOrder: filteredOrders | ||||
|          connectionsOrder: filteredOrders, | ||||
|          customIcons | ||||
|       }), options.value.passkey); | ||||
|  | ||||
|       // console.log(exportObj, JSON.parse(decrypt(exportObj, options.value.passkey))); | ||||
|   | ||||
| @@ -103,7 +103,7 @@ import { useI18n } from 'vue-i18n'; | ||||
| import BaseIcon from '@/components/BaseIcon.vue'; | ||||
| import BaseUploadInput from '@/components/BaseUploadInput.vue'; | ||||
| import { unproxify } from '@/libs/unproxify'; | ||||
| import { SidebarElement, useConnectionsStore } from '@/stores/connections'; | ||||
| import { CustomIcon, SidebarElement, useConnectionsStore } from '@/stores/connections'; | ||||
| import { useNotificationsStore } from '@/stores/notifications'; | ||||
|  | ||||
| const { t } = useI18n(); | ||||
| @@ -156,6 +156,7 @@ const importData = () => { | ||||
|             const importObj: { | ||||
|                connections: ConnectionParams[]; | ||||
|                connectionsOrder: SidebarElement[]; | ||||
|                customIcons: CustomIcon[]; | ||||
|             } = JSON.parse(decrypt(hash, options.value.passkey)); | ||||
|  | ||||
|             if (options.value.ignoreDuplicates) { | ||||
|   | ||||
| @@ -56,6 +56,7 @@ | ||||
|                      > | ||||
|                         <BaseIcon | ||||
|                            :icon-name="camelize(element.icon)" | ||||
|                            :type="element.hasCustomIcon ? 'custom' : 'mdi'" | ||||
|                            :size="36" | ||||
|                         /> | ||||
|                      </div> | ||||
|   | ||||
| @@ -70,6 +70,7 @@ | ||||
|                > | ||||
|                   <BaseIcon | ||||
|                      :icon-name="camelize(getConnectionOrderByUid(element).icon)" | ||||
|                      :type="getConnectionOrderByUid(element).hasCustomIcon ? 'custom' : 'mdi'" | ||||
|                      :size="36" | ||||
|                   /> | ||||
|                </div> | ||||
|   | ||||
| @@ -715,7 +715,7 @@ const openFile = async () => { | ||||
|    const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql', 'txt'] }] }); | ||||
|    if (result && !result.canceled) { | ||||
|       const file = result.filePaths[0]; | ||||
|       const content = await Application.readFile(file); | ||||
|       const content = await Application.readFile({ filePath: file, encoding: 'utf-8' }); | ||||
|       const fileName = file.split('/').pop().split('\\').pop(); | ||||
|       if (props.tab.filePath && props.tab.filePath !== file) { | ||||
|          newTab({ | ||||
| @@ -755,7 +755,7 @@ const saveFile = async () => { | ||||
| }; | ||||
|  | ||||
| const loadFileContent = async (file: string) => { | ||||
|    const content = await Application.readFile(file); | ||||
|    const content = await Application.readFile({ filePath: file, encoding: 'utf-8' }); | ||||
|    query.value = content; | ||||
|    lastSavedQuery.value = content; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user