refactor: initial iterations
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div v-if="!activeWorkspaceHandle">No Workspace Selected.</div>
|
||||
<div v-else class="flex-1">
|
||||
<div
|
||||
class="sticky z-10 flex flex-shrink-0 flex-col overflow-x-auto border-b border-dividerLight bg-primary"
|
||||
:style="{
|
||||
top: 0,
|
||||
}"
|
||||
>
|
||||
<WorkspaceCurrent :section="t('tab.collections')" />
|
||||
<input
|
||||
v-model="searchText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex h-8 w-full bg-transparent p-4 py-2"
|
||||
:placeholder="t('action.search')"
|
||||
/>
|
||||
</div>
|
||||
<NewCollectionsRest
|
||||
v-if="platform === 'rest'"
|
||||
:workspace-handle="activeWorkspaceHandle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useService } from "dioc/vue"
|
||||
import { ref } from "vue"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||
|
||||
defineProps<{
|
||||
platform: "rest" | "gql"
|
||||
}>()
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const searchText = ref("")
|
||||
|
||||
const workspaceService = useService(NewWorkspaceService)
|
||||
|
||||
const activeWorkspaceHandle = workspaceService.activeWorkspaceHandle
|
||||
|
||||
const showModalAdd = ref(false)
|
||||
const showModalImportExport = ref(false)
|
||||
</script>
|
||||
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="relative flex flex-col">
|
||||
<div
|
||||
class="z-3 group pointer-events-auto relative flex cursor-pointer items-stretch"
|
||||
@contextmenu.prevent="options?.tippy.show()"
|
||||
>
|
||||
<div
|
||||
class="flex min-w-0 flex-1 items-center justify-center"
|
||||
@click="emit('toggle-children')"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none flex items-center justify-center px-4"
|
||||
>
|
||||
<component :is="collectionIcon" class="svg-icons" />
|
||||
</span>
|
||||
<span
|
||||
class="pointer-events-none flex min-w-0 flex-1 py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ collection.name }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="!collectionReadonly" class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFilePlus"
|
||||
:title="t('request.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="emit('add-request')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFolderPlus"
|
||||
:title="t('folder.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="emit('add-folder')"
|
||||
/>
|
||||
<span>
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => tippyActions!.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.r="requestAction?.$el.click()"
|
||||
@keyup.n="folderAction?.$el.click()"
|
||||
@keyup.e="edit?.$el.click()"
|
||||
@keyup.delete="deleteAction?.$el.click()"
|
||||
@keyup.x="exportAction?.$el.click()"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
ref="requestAction"
|
||||
:icon="IconFilePlus"
|
||||
:label="t('request.new')"
|
||||
:shortcut="['R']"
|
||||
@click="
|
||||
() => {
|
||||
emit('add-request')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="folderAction"
|
||||
:icon="IconFolderPlus"
|
||||
:label="t('folder.new')"
|
||||
:shortcut="['N']"
|
||||
@click="
|
||||
() => {
|
||||
emit('add-folder')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="edit"
|
||||
:icon="IconEdit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click="
|
||||
() => {
|
||||
emit('edit-collection')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="exportAction"
|
||||
:icon="IconDownload"
|
||||
:label="t('export.title')"
|
||||
:shortcut="['X']"
|
||||
@click="
|
||||
() => {
|
||||
emit('export-data'),
|
||||
collectionsType === 'my-collections' ? hide() : null
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
emit('remove-collection')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RESTCollectionViewCollection } from "~/services/new-workspace/view"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { ref, computed } from "vue"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconFilePlus from "~icons/lucide/file-plus"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconFolder from "~icons/lucide/folder"
|
||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
collection: RESTCollectionViewCollection
|
||||
collectionReadonly: boolean
|
||||
isOpen: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "toggle-children"): void
|
||||
(event: "add-request"): void
|
||||
(event: "add-folder"): void
|
||||
(event: "edit-collection"): void
|
||||
(event: "export-data"): void
|
||||
(event: "remove-collection"): void
|
||||
}>()
|
||||
|
||||
const tippyActions = ref<TippyComponent | null>(null)
|
||||
const requestAction = ref<HTMLButtonElement | null>(null)
|
||||
const folderAction = ref<HTMLButtonElement | null>(null)
|
||||
const edit = ref<HTMLButtonElement | null>(null)
|
||||
const deleteAction = ref<HTMLButtonElement | null>(null)
|
||||
const exportAction = ref<HTMLButtonElement | null>(null)
|
||||
const options = ref<TippyComponent | null>(null)
|
||||
|
||||
const collectionIcon = computed(() => {
|
||||
return !props.isOpen ? IconFolder : IconFolderOpen
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="group flex items-stretch"
|
||||
@contextmenu.prevent="options?.tippy.show()"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-auto flex min-w-0 flex-1 cursor-pointer items-center justify-center"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none flex w-16 items-center justify-center truncate px-2"
|
||||
:class="requestLabelColor"
|
||||
:style="{ color: requestLabelColor }"
|
||||
>
|
||||
<span class="truncate text-tiny font-semibold">
|
||||
{{ request.method }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="pointer-events-none flex min-w-0 flex-1 items-center py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ request.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="isActive"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
class="relative mx-3 flex h-1.5 w-1.5 flex-shrink-0"
|
||||
:title="`${t('collection.request_in_use')}`"
|
||||
>
|
||||
<span
|
||||
class="absolute inline-flex h-full w-full flex-shrink-0 animate-ping rounded-full bg-green-500 opacity-75"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex h-1.5 w-1.5 flex-shrink-0 rounded-full bg-green-500"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="!collectionReadonly" class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconRotateCCW"
|
||||
:title="t('action.restore')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
/>
|
||||
<span>
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => tippyActions!.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.e="edit?.$el.click()"
|
||||
@keyup.d="duplicate?.$el.click()"
|
||||
@keyup.delete="deleteAction?.$el.click()"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
ref="edit"
|
||||
:icon="IconEdit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click="
|
||||
() => {
|
||||
emit('edit-request')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="duplicate"
|
||||
:icon="IconCopy"
|
||||
:label="t('action.duplicate')"
|
||||
:shortcut="['D']"
|
||||
@click="
|
||||
() => {
|
||||
emit('duplicate-request')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
emit('remove-request')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { RESTCollectionViewRequest } from "~/services/new-workspace/view"
|
||||
import { computed, ref } from "vue"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
request: RESTCollectionViewRequest
|
||||
collectionReadonly: boolean
|
||||
}>()
|
||||
|
||||
const tippyActions = ref<TippyComponent | null>(null)
|
||||
const options = ref<TippyComponent | null>(null)
|
||||
|
||||
// TODO: implement
|
||||
const isActive = ref(true)
|
||||
|
||||
const requestLabelColor = computed(() =>
|
||||
getMethodLabelColorClassOf(props.request)
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div v-if="collectionsAreReadonly !== undefined" class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky z-10 flex flex-1 justify-between border-b border-dividerLight bg-primary"
|
||||
:style="'top: var(--upper-primary-sticky-fold)'"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-if="collectionsAreReadonly"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
disabled
|
||||
class="!rounded-none"
|
||||
:icon="IconPlus"
|
||||
:title="t('team.no_access')"
|
||||
:label="t('add.new')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:icon="IconPlus"
|
||||
:label="t('add.new')"
|
||||
class="!rounded-none"
|
||||
@click="showModalAdd = true"
|
||||
/>
|
||||
<span class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/documentation/features/collections"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
:icon="IconHelpCircle"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconImport"
|
||||
:title="t('modal.import_export')"
|
||||
@click="onImportExportClick"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<HoppSmartTree :adapter="treeAdapter">
|
||||
<template
|
||||
#content="{ node, toggleChildren, isOpen, highlightChildren }"
|
||||
>
|
||||
<!-- TODO: Implement -->
|
||||
<NewCollectionsRestCollection
|
||||
v-if="node.data.type === 'collection'"
|
||||
:collection="node.data.value"
|
||||
:collection-readonly="collectionsAreReadonly"
|
||||
:is-open="isOpen"
|
||||
@toggle-children="toggleChildren"
|
||||
/>
|
||||
<div v-else @click="toggleChildren">
|
||||
{{ node.data.value }}
|
||||
</div>
|
||||
</template>
|
||||
<template #emptyNode="{ node }">
|
||||
<!-- TODO: Implement -->
|
||||
<div>Empty Node!</div>
|
||||
</template>
|
||||
</HoppSmartTree>
|
||||
</div>
|
||||
<CollectionsAdd
|
||||
:show="showModalAdd"
|
||||
:loading-state="modalLoadingState"
|
||||
@submit="addNewRootCollection"
|
||||
@hide-modal="showModalAdd = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as E from "fp-ts/lib/Either"
|
||||
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import { HandleRef } from "~/services/new-workspace/handle"
|
||||
import { Workspace } from "~/services/new-workspace/workspace"
|
||||
import { computed, markRaw, ref } from "vue"
|
||||
import { WorkspaceRESTCollectionTreeAdapter } from "~/helpers/adapters/WorkspaceRESTCollectionTreeAdapter"
|
||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||
import { useService } from "dioc/vue"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
workspaceHandle: HandleRef<Workspace>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "display-modal-add"): void
|
||||
(e: "display-modal-import-export"): void
|
||||
}>()
|
||||
|
||||
const workspaceService = useService(NewWorkspaceService)
|
||||
const treeAdapter = markRaw(
|
||||
new WorkspaceRESTCollectionTreeAdapter(
|
||||
props.workspaceHandle,
|
||||
workspaceService
|
||||
)
|
||||
)
|
||||
|
||||
const collectionsAreReadonly = computed(() => {
|
||||
if (props.workspaceHandle.value.type === "ok") {
|
||||
return props.workspaceHandle.value.data.collectionsAreReadonly
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
|
||||
const showModalAdd = ref(false)
|
||||
const modalLoadingState = ref(false)
|
||||
|
||||
async function addNewRootCollection(name: string) {
|
||||
modalLoadingState.value = true
|
||||
|
||||
const result = await workspaceService.createRESTRootCollection(
|
||||
props.workspaceHandle,
|
||||
name
|
||||
)
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
// TODO: Error Handling
|
||||
return
|
||||
}
|
||||
|
||||
// Workspace invalidated
|
||||
if (result.right.value.type === "invalid") {
|
||||
// TODO: Error Handling
|
||||
return
|
||||
}
|
||||
|
||||
modalLoadingState.value = false
|
||||
showModalAdd.value = false
|
||||
}
|
||||
|
||||
function onImportExportClick() {
|
||||
// TODO: Implement
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user