Collection.vue (hoppscotch-2.2.1) | : | Collection.vue (hoppscotch-3.0.0) | ||
---|---|---|---|---|
<template> | <template> | |||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]"> | <div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]"> | |||
<div | <div | |||
class="flex items-stretch group" | class="flex items-stretch group" | |||
@dragover.prevent | @dragover.prevent | |||
@drop.prevent="dropEvent" | @drop.prevent="dropEvent" | |||
@dragover="dragging = true" | @dragover="dragging = true" | |||
@drop="dragging = false" | @drop="dragging = false" | |||
@dragleave="dragging = false" | @dragleave="dragging = false" | |||
@dragend="dragging = false" | @dragend="dragging = false" | |||
@contextmenu.prevent="options.tippy().show()" | @contextmenu.prevent="options.tippy.show()" | |||
> | > | |||
<span | <span | |||
class="cursor-pointer flex px-4 items-center justify-center" | class="flex items-center justify-center px-4 cursor-pointer" | |||
@click="toggleShowChildren()" | @click="toggleShowChildren()" | |||
> | > | |||
<SmartIcon | <component | |||
:is="collectionIcon" | ||||
class="svg-icons" | class="svg-icons" | |||
:class="{ 'text-accent': isSelected }" | :class="{ 'text-accent': isSelected }" | |||
:name="getCollectionIcon" | ||||
/> | /> | |||
</span> | </span> | |||
<span | <span | |||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition group-hov er:text-secondaryDark" | class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hov er:text-secondaryDark" | |||
@click="toggleShowChildren()" | @click="toggleShowChildren()" | |||
> | > | |||
<span class="truncate" :class="{ 'text-accent': isSelected }"> | <span class="truncate" :class="{ 'text-accent': isSelected }"> | |||
{{ collection.name }} | {{ collection.name }} | |||
</span> | </span> | |||
</span> | </span> | |||
<div class="flex"> | <div class="flex"> | |||
<ButtonSecondary | <ButtonSecondary | |||
v-tippy="{ theme: 'tooltip' }" | v-tippy="{ theme: 'tooltip' }" | |||
svg="folder-plus" | :icon="IconFilePlus" | |||
:title="$t('folder.new')" | :title="t('request.new')" | |||
class="hidden group-hover:inline-flex" | class="hidden group-hover:inline-flex" | |||
@click.native=" | @click=" | |||
$emit('add-folder', { | emit('add-request', { | |||
path: `${collectionIndex}`, | ||||
}) | ||||
" | ||||
/> | ||||
<ButtonSecondary | ||||
v-tippy="{ theme: 'tooltip' }" | ||||
:icon="IconFolderPlus" | ||||
:title="t('folder.new')" | ||||
class="hidden group-hover:inline-flex" | ||||
@click=" | ||||
emit('add-folder', { | ||||
path: `${collectionIndex}`, | path: `${collectionIndex}`, | |||
}) | }) | |||
" | " | |||
/> | /> | |||
<span> | <span> | |||
<tippy | <tippy | |||
ref="options" | ref="options" | |||
interactive | interactive | |||
trigger="click" | trigger="click" | |||
theme="popover" | theme="popover" | |||
arrow | arrow | |||
:on-shown="() => tippyActions.focus()" | :on-shown="() => tippyActions.focus()" | |||
> | > | |||
<template #trigger> | <ButtonSecondary | |||
<ButtonSecondary | v-tippy="{ theme: 'tooltip' }" | |||
v-tippy="{ theme: 'tooltip' }" | :title="t('action.more')" | |||
:title="$t('action.more')" | :icon="IconMoreVertical" | |||
svg="more-vertical" | /> | |||
/> | ||||
<template #content="{ hide }"> | ||||
<div | ||||
ref="tippyActions" | ||||
class="flex flex-col focus:outline-none" | ||||
tabindex="0" | ||||
role="menu" | ||||
@keyup.r="requestAction.$el.click()" | ||||
@keyup.n="folderAction.$el.click()" | ||||
@keyup.e="edit.$el.click()" | ||||
@keyup.delete="deleteAction.$el.click()" | ||||
@keyup.escape="hide()" | ||||
> | ||||
<SmartItem | ||||
ref="requestAction" | ||||
:icon="IconFilePlus" | ||||
:label="`${t('request.new')}`" | ||||
:shortcut="['R']" | ||||
@click=" | ||||
() => { | ||||
$emit('add-request', { | ||||
path: `${collectionIndex}`, | ||||
}) | ||||
hide() | ||||
} | ||||
" | ||||
/> | ||||
<SmartItem | ||||
ref="folderAction" | ||||
:icon="IconFolderPlus" | ||||
:label="`${t('folder.new')}`" | ||||
:shortcut="['N']" | ||||
@click=" | ||||
() => { | ||||
emit('add-folder', { | ||||
path: `${collectionIndex}`, | ||||
}) | ||||
hide() | ||||
} | ||||
" | ||||
/> | ||||
<SmartItem | ||||
ref="edit" | ||||
:icon="IconEdit" | ||||
:label="`${t('action.edit')}`" | ||||
:shortcut="['E']" | ||||
@click=" | ||||
() => { | ||||
emit('edit-collection') | ||||
hide() | ||||
} | ||||
" | ||||
/> | ||||
<SmartItem | ||||
ref="deleteAction" | ||||
:icon="IconTrash2" | ||||
:label="`${t('action.delete')}`" | ||||
:shortcut="['⌫']" | ||||
@click=" | ||||
() => { | ||||
confirmRemove = true | ||||
hide() | ||||
} | ||||
" | ||||
/> | ||||
</div> | ||||
</template> | </template> | |||
<div | ||||
ref="tippyActions" | ||||
class="flex flex-col focus:outline-none" | ||||
tabindex="0" | ||||
@keyup.n="folderAction.$el.click()" | ||||
@keyup.e="edit.$el.click()" | ||||
@keyup.delete="deleteAction.$el.click()" | ||||
@keyup.escape="options.tippy().hide()" | ||||
> | ||||
<SmartItem | ||||
ref="folderAction" | ||||
svg="folder-plus" | ||||
:label="`${$t('folder.new')}`" | ||||
:shortcut="['N']" | ||||
@click.native=" | ||||
() => { | ||||
$emit('add-folder', { | ||||
path: `${collectionIndex}`, | ||||
}) | ||||
options.tippy().hide() | ||||
} | ||||
" | ||||
/> | ||||
<SmartItem | ||||
ref="edit" | ||||
svg="edit" | ||||
:label="`${$t('action.edit')}`" | ||||
:shortcut="['E']" | ||||
@click.native=" | ||||
() => { | ||||
$emit('edit-collection') | ||||
options.tippy().hide() | ||||
} | ||||
" | ||||
/> | ||||
<SmartItem | ||||
ref="deleteAction" | ||||
svg="trash-2" | ||||
:label="`${$t('action.delete')}`" | ||||
:shortcut="['⌫']" | ||||
@click.native=" | ||||
() => { | ||||
confirmRemove = true | ||||
options.tippy().hide() | ||||
} | ||||
" | ||||
/> | ||||
</div> | ||||
</tippy> | </tippy> | |||
</span> | </span> | |||
</div> | </div> | |||
</div> | </div> | |||
<div v-if="showChildren || isFiltered" class="flex"> | <div v-if="showChildren || isFiltered" class="flex"> | |||
<div | <div | |||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125" | class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125" | |||
@click="toggleShowChildren()" | @click="toggleShowChildren()" | |||
></div> | ></div> | |||
<div class="flex flex-col flex-1 truncate"> | <div class="flex flex-col flex-1 truncate"> | |||
<CollectionsGraphqlFolder | <CollectionsGraphqlFolder | |||
v-for="(folder, index) in collection.folders" | v-for="(folder, index) in collection.folders" | |||
:key="`folder-${String(index)}`" | :key="`folder-${String(index)}`" | |||
:picked="picked" | :picked="picked" | |||
:saving-mode="savingMode" | :saving-mode="savingMode" | |||
:folder="folder" | :folder="folder" | |||
:folder-index="index" | :folder-index="index" | |||
:folder-path="`${collectionIndex}/${String(index)}`" | :folder-path="`${collectionIndex}/${String(index)}`" | |||
:collection-index="collectionIndex" | :collection-index="collectionIndex" | |||
:doc="doc" | ||||
:is-filtered="isFiltered" | :is-filtered="isFiltered" | |||
@add-request="$emit('add-request', $event)" | ||||
@add-folder="$emit('add-folder', $event)" | @add-folder="$emit('add-folder', $event)" | |||
@edit-folder="$emit('edit-folder', $event)" | @edit-folder="$emit('edit-folder', $event)" | |||
@edit-request="$emit('edit-request', $event)" | @edit-request="$emit('edit-request', $event)" | |||
@duplicate-request="$emit('duplicate-request', $event)" | @duplicate-request="$emit('duplicate-request', $event)" | |||
@select="$emit('select', $event)" | @select="$emit('select', $event)" | |||
/> | /> | |||
<CollectionsGraphqlRequest | <CollectionsGraphqlRequest | |||
v-for="(request, index) in collection.requests" | v-for="(request, index) in collection.requests" | |||
:key="`request-${String(index)}`" | :key="`request-${String(index)}`" | |||
:picked="picked" | :picked="picked" | |||
:saving-mode="savingMode" | :saving-mode="savingMode" | |||
:request="request" | :request="request" | |||
:collection-index="collectionIndex" | :collection-index="collectionIndex" | |||
:folder-index="-1" | :folder-index="-1" | |||
:folder-name="collection.name" | :folder-name="collection.name" | |||
:folder-path="`${collectionIndex}`" | :folder-path="`${collectionIndex}`" | |||
:request-index="index" | :request-index="index" | |||
:doc="doc" | ||||
@edit-request="$emit('edit-request', $event)" | @edit-request="$emit('edit-request', $event)" | |||
@duplicate-request="$emit('duplicate-request', $event)" | @duplicate-request="$emit('duplicate-request', $event)" | |||
@select="$emit('select', $event)" | @select="$emit('select', $event)" | |||
/> | /> | |||
<div | <div | |||
v-if=" | v-if=" | |||
collection.folders.length === 0 && collection.requests.length === 0 | collection.folders.length === 0 && collection.requests.length === 0 | |||
" | " | |||
class="flex flex-col text-secondaryLight p-4 items-center justify-cent er" | class="flex flex-col items-center justify-center p-4 text-secondaryLig ht" | |||
> | > | |||
<img | <img | |||
:src="`/images/states/${$colorMode.value}/pack.svg`" | :src="`/images/states/${colorMode.value}/pack.svg`" | |||
loading="lazy" | loading="lazy" | |||
class="flex-col object-contain object-center h-16 mb-4 w-16 inline-f | class="inline-flex flex-col object-contain object-center w-16 h-16 m | |||
lex" | b-4" | |||
:alt="`${$t('empty.collection')}`" | :alt="`${t('empty.collection')}`" | |||
/> | /> | |||
<span class="text-center"> | <span class="text-center"> | |||
{{ $t("empty.collection") }} | {{ t("empty.collection") }} | |||
</span> | </span> | |||
</div> | </div> | |||
</div> | </div> | |||
</div> | </div> | |||
<SmartConfirmModal | <SmartConfirmModal | |||
:show="confirmRemove" | :show="confirmRemove" | |||
:title="`${$t('confirm.remove_collection')}`" | :title="`${t('confirm.remove_collection')}`" | |||
@hide-modal="confirmRemove = false" | @hide-modal="confirmRemove = false" | |||
@resolve="removeCollection" | @resolve="removeCollection" | |||
/> | /> | |||
</div> | </div> | |||
</template> | </template> | |||
<script lang="ts"> | <script setup lang="ts"> | |||
import { defineComponent, ref } from "@nuxtjs/composition-api" | import { computed, ref } from "vue" | |||
import IconCheckCircle from "~icons/lucide/check-circle" | ||||
import IconFolder from "~icons/lucide/folder" | ||||
import IconFolderOpen from "~icons/lucide/folder-open" | ||||
import IconFilePlus from "~icons/lucide/file-plus" | ||||
import IconFolderPlus from "~icons/lucide/folder-plus" | ||||
import IconMoreVertical from "~icons/lucide/more-vertical" | ||||
import IconEdit from "~icons/lucide/edit" | ||||
import IconTrash2 from "~icons/lucide/trash-2" | ||||
import { useToast } from "@composables/toast" | ||||
import { useI18n } from "@composables/i18n" | ||||
import { useColorMode } from "@composables/theming" | ||||
import { | import { | |||
removeGraphqlCollection, | removeGraphqlCollection, | |||
moveGraphqlRequest, | moveGraphqlRequest, | |||
} from "~/newstore/collections" | } from "~/newstore/collections" | |||
export default defineComponent({ | const props = defineProps({ | |||
props: { | picked: { type: Object, default: null }, | |||
picked: { type: Object, default: null }, | // Whether the viewing context is related to picking (activates 'select' event | |||
// Whether the viewing context is related to picking (activates 'select' eve | s) | |||
nts) | savingMode: { type: Boolean, default: false }, | |||
savingMode: { type: Boolean, default: false }, | collectionIndex: { type: Number, default: null }, | |||
collectionIndex: { type: Number, default: null }, | collection: { type: Object, default: () => ({}) }, | |||
collection: { type: Object, default: () => {} }, | isFiltered: Boolean, | |||
doc: Boolean, | }) | |||
isFiltered: Boolean, | ||||
}, | ||||
setup() { | ||||
return { | ||||
tippyActions: ref<any | null>(null), | ||||
options: ref<any | null>(null), | ||||
folderAction: ref<any | null>(null), | ||||
edit: ref<any | null>(null), | ||||
deleteAction: ref<any | null>(null), | ||||
} | ||||
}, | ||||
data() { | ||||
return { | ||||
showChildren: false, | ||||
dragging: false, | ||||
selectedFolder: {}, | ||||
confirmRemove: false, | ||||
} | ||||
}, | ||||
computed: { | ||||
isSelected(): boolean { | ||||
return ( | ||||
this.picked && | ||||
this.picked.pickedType === "gql-my-collection" && | ||||
this.picked.collectionIndex === this.collectionIndex | ||||
) | ||||
}, | ||||
getCollectionIcon() { | ||||
if (this.isSelected) return "check-circle" | ||||
else if (!this.showChildren && !this.isFiltered) return "folder" | ||||
else if (this.showChildren || this.isFiltered) return "folder-open" | ||||
else return "folder" | ||||
}, | ||||
}, | ||||
methods: { | ||||
pick() { | ||||
this.$emit("select", { | ||||
picked: { | ||||
pickedType: "gql-my-collection", | ||||
collectionIndex: this.collectionIndex, | ||||
}, | ||||
}) | ||||
}, | ||||
toggleShowChildren() { | ||||
if (this.savingMode) { | ||||
this.pick() | ||||
} | ||||
this.showChildren = !this.showChildren | const colorMode = useColorMode() | |||
}, | const toast = useToast() | |||
removeCollection() { | const t = useI18n() | |||
// Cancel pick if picked collection is deleted | ||||
if ( | // TODO: improve types plz | |||
this.picked && | const emit = defineEmits<{ | |||
this.picked.pickedType === "gql-my-collection" && | (e: "select", i: { picked: any }): void | |||
this.picked.collectionIndex === this.collectionIndex | (e: "edit-request", i: any): void | |||
) { | (e: "duplicate-request", i: any): void | |||
this.$emit("select", { picked: null }) | (e: "add-request", i: any): void | |||
} | (e: "add-folder", i: any): void | |||
removeGraphqlCollection(this.collectionIndex) | (e: "edit-folder", i: any): void | |||
this.$toast.success(`${this.$t("state.deleted")}`) | (e: "edit-collection"): void | |||
}, | }>() | |||
dropEvent({ dataTransfer }: any) { | ||||
this.dragging = !this.dragging | const tippyActions = ref<any | null>(null) | |||
const folderPath = dataTransfer.getData("folderPath") | const options = ref<any | null>(null) | |||
const requestIndex = dataTransfer.getData("requestIndex") | const requestAction = ref<any | null>(null) | |||
moveGraphqlRequest(folderPath, requestIndex, `${this.collectionIndex}`) | const folderAction = ref<any | null>(null) | |||
}, | const edit = ref<any | null>(null) | |||
}, | const deleteAction = ref<any | null>(null) | |||
const showChildren = ref(false) | ||||
const dragging = ref(false) | ||||
const confirmRemove = ref(false) | ||||
const isSelected = computed( | ||||
() => | ||||
props.picked?.pickedType === "gql-my-collection" && | ||||
props.picked?.collectionIndex === props.collectionIndex | ||||
) | ||||
const collectionIcon = computed(() => { | ||||
if (isSelected.value) return IconCheckCircle | ||||
else if (!showChildren.value && !props.isFiltered) return IconFolder | ||||
else if (!showChildren.value || props.isFiltered) return IconFolderOpen | ||||
else return IconFolder | ||||
}) | }) | |||
const pick = () => { | ||||
emit("select", { | ||||
picked: { | ||||
pickedType: "gql-my-collection", | ||||
collectionIndex: props.collectionIndex, | ||||
}, | ||||
}) | ||||
} | ||||
const toggleShowChildren = () => { | ||||
if (props.savingMode) { | ||||
pick() | ||||
} | ||||
showChildren.value = !showChildren.value | ||||
} | ||||
const removeCollection = () => { | ||||
// Cancel pick if picked collection is deleted | ||||
if ( | ||||
props.picked?.pickedType === "gql-my-collection" && | ||||
props.picked?.collectionIndex === props.collectionIndex | ||||
) { | ||||
emit("select", { picked: null }) | ||||
} | ||||
removeGraphqlCollection(props.collectionIndex) | ||||
toast.success(`${t("state.deleted")}`) | ||||
} | ||||
const dropEvent = ({ dataTransfer }: any) => { | ||||
dragging.value = !dragging.value | ||||
const folderPath = dataTransfer.getData("folderPath") | ||||
const requestIndex = dataTransfer.getData("requestIndex") | ||||
moveGraphqlRequest(folderPath, requestIndex, `${props.collectionIndex}`) | ||||
} | ||||
</script> | </script> | |||
End of changes. 21 change blocks. | ||||
151 lines changed or deleted | 194 lines changed or added |