318 lines
10 KiB
Vue
318 lines
10 KiB
Vue
<template>
|
|
<div class="w-80 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col">
|
|
<!-- Header -->
|
|
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Collections</h2>
|
|
<div class="flex items-center gap-2">
|
|
<rs-button
|
|
variant="secondary-outline"
|
|
size="sm"
|
|
@click="showCreateCollectionModal = true"
|
|
class="p-2"
|
|
>
|
|
<Icon name="ic:outline-add" size="16" />
|
|
</rs-button>
|
|
<rs-button
|
|
variant="text"
|
|
size="sm"
|
|
@click="$emit('close')"
|
|
class="p-2 lg:hidden"
|
|
>
|
|
<Icon name="ic:outline-close" size="16" />
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Requests -->
|
|
<div v-if="requestHistory.length > 0" class="border-b border-gray-200 dark:border-gray-700 p-4">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-white">Recent</h3>
|
|
<span class="text-xs text-gray-500 dark:text-gray-400">Last 5 requests</span>
|
|
</div>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">Your most recently sent requests for quick access</p>
|
|
<div class="space-y-1">
|
|
<div
|
|
v-for="request in requestHistory.slice(0, 5)"
|
|
:key="request.id"
|
|
@click="loadHistoryRequest(request)"
|
|
class="flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
|
|
>
|
|
<rs-badge :variant="getMethodVariant(request.method)" size="sm" class="w-12 text-xs">
|
|
{{ request.method }}
|
|
</rs-badge>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm text-gray-700 dark:text-gray-300 truncate">{{ request.name }}</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(request.timestamp) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Collections List -->
|
|
<div class="flex-1 overflow-y-auto p-2">
|
|
<div v-if="collections.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
<Icon name="ic:outline-folder-open" size="48" class="mx-auto mb-4 opacity-50" />
|
|
<p class="text-sm mb-4">No collections yet</p>
|
|
<div class="flex justify-center">
|
|
<rs-button
|
|
variant="primary"
|
|
size="sm"
|
|
@click="showCreateCollectionModal = true"
|
|
class="flex items-center gap-2"
|
|
>
|
|
<Icon name="ic:outline-add" size="14" />
|
|
Create Collection
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="space-y-1">
|
|
<div
|
|
v-for="collection in collections"
|
|
:key="collection.id"
|
|
class="group"
|
|
>
|
|
<!-- Collection Header -->
|
|
<div
|
|
@click="toggleCollection(collection.id)"
|
|
class="flex items-center gap-2 p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer group/header"
|
|
>
|
|
<Icon
|
|
:name="expandedCollections.has(collection.id) ? 'ic:outline-expand-more' : 'ic:outline-chevron-right'"
|
|
size="16"
|
|
class="text-gray-400 transition-transform"
|
|
/>
|
|
<Icon name="ic:outline-folder" size="16" class="text-blue-500" />
|
|
<span class="flex-1 text-sm font-medium text-gray-900 dark:text-white truncate">
|
|
{{ collection.name }}
|
|
</span>
|
|
<div class="opacity-0 group-hover/header:opacity-100 flex items-center gap-1">
|
|
<rs-button
|
|
variant="text"
|
|
size="sm"
|
|
@click.stop="editCollection(collection)"
|
|
class="p-1"
|
|
>
|
|
<Icon name="ic:outline-edit" size="14" />
|
|
</rs-button>
|
|
<rs-button
|
|
variant="text"
|
|
size="sm"
|
|
@click.stop="deleteCollection(collection.id)"
|
|
class="p-1 text-red-500 hover:text-red-600"
|
|
>
|
|
<Icon name="ic:outline-delete" size="14" />
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Collection Requests -->
|
|
<div v-if="expandedCollections.has(collection.id)" class="ml-6 space-y-1">
|
|
<div
|
|
v-for="request in collection.requests"
|
|
:key="request.id"
|
|
@click="loadRequest(request)"
|
|
class="flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer group/request"
|
|
>
|
|
<rs-badge :variant="getMethodVariant(request.method)" size="sm" class="w-12 text-xs">
|
|
{{ request.method }}
|
|
</rs-badge>
|
|
<span class="flex-1 text-sm text-gray-700 dark:text-gray-300 truncate">
|
|
{{ request.name }}
|
|
</span>
|
|
<div class="opacity-0 group-hover/request:opacity-100 flex items-center gap-1">
|
|
<rs-button
|
|
variant="text"
|
|
size="sm"
|
|
@click.stop="editRequest(request)"
|
|
class="p-1"
|
|
>
|
|
<Icon name="ic:outline-edit" size="12" />
|
|
</rs-button>
|
|
<rs-button
|
|
variant="text"
|
|
size="sm"
|
|
@click.stop="deleteRequest(collection.id, request.id)"
|
|
class="p-1 text-red-500 hover:text-red-600"
|
|
>
|
|
<Icon name="ic:outline-delete" size="12" />
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Request Button -->
|
|
<rs-button
|
|
variant="text"
|
|
size="sm"
|
|
@click="addRequestToCollection(collection.id)"
|
|
class="w-full justify-start text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
|
>
|
|
<Icon name="ic:outline-add" size="14" class="mr-2" />
|
|
Add Request
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Collection Modal -->
|
|
<CreateCollectionModal
|
|
v-if="showCreateCollectionModal"
|
|
@close="showCreateCollectionModal = false"
|
|
@created="onCollectionCreated"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import CreateCollectionModal from './CreateCollectionModal.vue'
|
|
|
|
const emit = defineEmits(['close'])
|
|
|
|
const { getMethodVariant } = useApiRequest()
|
|
const {
|
|
collections,
|
|
requestHistory,
|
|
httpMethod,
|
|
requestUrl,
|
|
requestParams,
|
|
requestHeaders,
|
|
requestAuth,
|
|
requestBody,
|
|
requestName,
|
|
showNotification
|
|
} = useApiPlatform()
|
|
|
|
const showCreateCollectionModal = ref(false)
|
|
const expandedCollections = ref(new Set())
|
|
|
|
// Initialize with all collections expanded
|
|
watch(collections, (newCollections) => {
|
|
newCollections.forEach(collection => {
|
|
expandedCollections.value.add(collection.id)
|
|
})
|
|
}, { immediate: true })
|
|
|
|
const toggleCollection = (collectionId) => {
|
|
if (expandedCollections.value.has(collectionId)) {
|
|
expandedCollections.value.delete(collectionId)
|
|
} else {
|
|
expandedCollections.value.add(collectionId)
|
|
}
|
|
}
|
|
|
|
const loadRequest = (request) => {
|
|
httpMethod.value = request.method
|
|
requestUrl.value = request.url
|
|
requestName.value = request.name
|
|
|
|
// Load request data if available
|
|
if (request.params) requestParams.value = request.params
|
|
if (request.headers) requestHeaders.value = request.headers
|
|
if (request.auth) requestAuth.value = request.auth
|
|
if (request.body) requestBody.value = request.body
|
|
|
|
showNotification(`Loaded request: ${request.name}`, 'success')
|
|
}
|
|
|
|
const loadHistoryRequest = (request) => {
|
|
httpMethod.value = request.method
|
|
requestUrl.value = request.url
|
|
requestName.value = request.name
|
|
|
|
showNotification(`Loaded from history: ${request.name}`, 'success')
|
|
}
|
|
|
|
const addRequestToCollection = (collectionId) => {
|
|
const collection = collections.value.find(c => c.id === collectionId)
|
|
if (!collection) return
|
|
|
|
const newRequest = {
|
|
id: Date.now(),
|
|
name: 'New Request',
|
|
method: 'GET',
|
|
url: '',
|
|
params: [],
|
|
headers: [],
|
|
auth: { type: 'none' },
|
|
body: { type: 'raw', raw: '' }
|
|
}
|
|
|
|
collection.requests.push(newRequest)
|
|
saveCollections()
|
|
showNotification('Request added to collection', 'success')
|
|
}
|
|
|
|
const editCollection = (collection) => {
|
|
const newName = prompt('Collection name:', collection.name)
|
|
if (newName && newName.trim()) {
|
|
collection.name = newName.trim()
|
|
saveCollections()
|
|
showNotification('Collection updated', 'success')
|
|
}
|
|
}
|
|
|
|
const deleteCollection = (collectionId) => {
|
|
if (confirm('Are you sure you want to delete this collection?')) {
|
|
const index = collections.value.findIndex(c => c.id === collectionId)
|
|
if (index !== -1) {
|
|
collections.value.splice(index, 1)
|
|
saveCollections()
|
|
showNotification('Collection deleted', 'success')
|
|
}
|
|
}
|
|
}
|
|
|
|
const editRequest = (request) => {
|
|
const newName = prompt('Request name:', request.name)
|
|
if (newName && newName.trim()) {
|
|
request.name = newName.trim()
|
|
saveCollections()
|
|
showNotification('Request updated', 'success')
|
|
}
|
|
}
|
|
|
|
const deleteRequest = (collectionId, requestId) => {
|
|
if (confirm('Are you sure you want to delete this request?')) {
|
|
const collection = collections.value.find(c => c.id === collectionId)
|
|
if (collection) {
|
|
const index = collection.requests.findIndex(r => r.id === requestId)
|
|
if (index !== -1) {
|
|
collection.requests.splice(index, 1)
|
|
saveCollections()
|
|
showNotification('Request deleted', 'success')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const onCollectionCreated = (collection) => {
|
|
collections.value.push(collection)
|
|
expandedCollections.value.add(collection.id)
|
|
saveCollections()
|
|
showNotification('Collection created', 'success')
|
|
}
|
|
|
|
const saveCollections = () => {
|
|
localStorage.setItem('api-platform-collections', JSON.stringify(collections.value))
|
|
}
|
|
|
|
const formatDate = (timestamp) => {
|
|
return new Date(timestamp).toLocaleString()
|
|
}
|
|
|
|
// Load collections from localStorage on mount
|
|
onMounted(() => {
|
|
const saved = localStorage.getItem('api-platform-collections')
|
|
if (saved) {
|
|
try {
|
|
const parsed = JSON.parse(saved)
|
|
collections.value.splice(0, collections.value.length, ...parsed)
|
|
} catch (e) {
|
|
console.error('Failed to load collections:', e)
|
|
}
|
|
}
|
|
})
|
|
</script> |