|
| 1 | +<script setup lang="ts"> |
| 2 | +import { ref, reactive, watch } from 'vue' |
| 3 | +import { useClipboard } from '@vueuse/core' |
| 4 | +import { useEnhancedToast } from '@/shared/composables/useEnhancedToast' |
| 5 | +import { |
| 6 | + Dialog, |
| 7 | + DialogContent, |
| 8 | + DialogHeader, |
| 9 | + DialogTitle, |
| 10 | + DialogDescription, |
| 11 | +} from '@/shared/ui/dialog' |
| 12 | +import { Label } from '@/shared/ui/label' |
| 13 | +import { Button } from '@/shared/ui/button' |
| 14 | +import { Switch } from '@/shared/ui/switch' |
| 15 | +import { api } from '@/shared/utils/api' |
| 16 | +import { useAsyncTask } from '@/shared/composables/useAsyncTask' |
| 17 | +import { Icon } from '@iconify/vue' |
| 18 | +
|
| 19 | +const props = defineProps<{ |
| 20 | + open: boolean |
| 21 | + eventId: number |
| 22 | + onClose: () => void |
| 23 | +}>() |
| 24 | +
|
| 25 | +const settings = reactive({ |
| 26 | + includeDescriptions: true, |
| 27 | + includeExamples: true, |
| 28 | + additionalProperties: true, |
| 29 | + format: 'yaml' as 'yaml' | 'json', |
| 30 | +}) |
| 31 | +
|
| 32 | +const preview = ref('') |
| 33 | +const updates = ref(0) |
| 34 | +const { run: fetchSchemaPreview, isLoading } = useAsyncTask() |
| 35 | +
|
| 36 | +const { copy: copyJson, isSupported } = useClipboard({ source: preview }) |
| 37 | +const { showCopied, showCopyError } = useEnhancedToast() |
| 38 | +const handleCopy = async () => { |
| 39 | + try { |
| 40 | + await copyJson() |
| 41 | + showCopied('Schema') |
| 42 | + } catch { |
| 43 | + showCopyError('Schema') |
| 44 | + } |
| 45 | +} |
| 46 | +
|
| 47 | +function handleFetch() { |
| 48 | + const params = { |
| 49 | + include_descriptions: settings.includeDescriptions, |
| 50 | + include_examples: settings.includeExamples, |
| 51 | + additional_properties: settings.additionalProperties, |
| 52 | + } |
| 53 | + const format = settings.format |
| 54 | +
|
| 55 | + fetchSchemaPreview(async () => { |
| 56 | + const response = await api.get(`/events/${props.eventId}/schema.${format}`, { |
| 57 | + params, |
| 58 | + responseType: 'text', |
| 59 | + }) |
| 60 | +
|
| 61 | + preview.value = |
| 62 | + format === 'json' ? JSON.stringify(JSON.parse(response.data), null, 2) : response.data |
| 63 | + updates.value += 1 |
| 64 | + }) |
| 65 | +} |
| 66 | +
|
| 67 | +watch( |
| 68 | + () => props.open, |
| 69 | + isOpen => { |
| 70 | + if (isOpen) { |
| 71 | + preview.value = '' |
| 72 | + handleFetch() |
| 73 | + } |
| 74 | + }, |
| 75 | + { immediate: true } |
| 76 | +) |
| 77 | +</script> |
| 78 | + |
| 79 | +<template> |
| 80 | + <Dialog :open="props.open" @update:open="props.onClose"> |
| 81 | + <DialogContent class="!max-w-4xl"> |
| 82 | + <DialogHeader> |
| 83 | + <DialogTitle>Export Schema</DialogTitle> |
| 84 | + <DialogDescription> |
| 85 | + Generate a Swagger schema for the event. You can customize the settings below to include |
| 86 | + or exclude certain properties. |
| 87 | + </DialogDescription> |
| 88 | + </DialogHeader> |
| 89 | + |
| 90 | + <div class="mt-4 grid grid-cols-1 gap-8 md:grid-cols-[1fr_2fr]"> |
| 91 | + <!-- Settings Column --> |
| 92 | + <div class="space-y-6"> |
| 93 | + <div class="flex items-center justify-between"> |
| 94 | + <Label for="includeDescriptions">Include descriptions</Label> |
| 95 | + <Switch id="includeDescriptions" v-model="settings.includeDescriptions" /> |
| 96 | + </div> |
| 97 | + |
| 98 | + <div class="flex items-center justify-between"> |
| 99 | + <Label for="includeExamples">Include examples</Label> |
| 100 | + <Switch id="includeExamples" v-model="settings.includeExamples" /> |
| 101 | + </div> |
| 102 | + |
| 103 | + <div class="flex items-center justify-between"> |
| 104 | + <Label for="additionalProperties">Allow additional properties</Label> |
| 105 | + <Switch id="additionalProperties" v-model="settings.additionalProperties" /> |
| 106 | + </div> |
| 107 | + |
| 108 | + <div class="mt-12 flex items-center justify-between"> |
| 109 | + <Label for="format">Schema format</Label> |
| 110 | + <div class="flex gap-2"> |
| 111 | + <Button |
| 112 | + class="w-16 font-mono" |
| 113 | + :variant="settings.format === 'yaml' ? 'default' : 'outline'" |
| 114 | + size="sm" |
| 115 | + @click="settings.format = 'yaml'" |
| 116 | + > |
| 117 | + YAML |
| 118 | + </Button> |
| 119 | + <Button |
| 120 | + class="w-16 font-mono" |
| 121 | + :variant="settings.format === 'json' ? 'default' : 'outline'" |
| 122 | + size="sm" |
| 123 | + @click="settings.format = 'json'" |
| 124 | + > |
| 125 | + JSON |
| 126 | + </Button> |
| 127 | + </div> |
| 128 | + </div> |
| 129 | + |
| 130 | + <div> |
| 131 | + <Button variant="secondary" class="w-full" @click="handleFetch" :disabled="isLoading"> |
| 132 | + Update |
| 133 | + </Button> |
| 134 | + </div> |
| 135 | + </div> |
| 136 | + |
| 137 | + <!-- Preview Column --> |
| 138 | + <div> |
| 139 | + <!-- <Textarea class="h-[400px] resize-none font-mono text-sm" :value="preview" readonly /> --> |
| 140 | + |
| 141 | + <div |
| 142 | + side="bottom" |
| 143 | + @click.stop |
| 144 | + class="border-muted-foreground bg-foreground text-background relative h-[400px] overflow-y-auto rounded-md border p-4 text-left shadow-sm" |
| 145 | + > |
| 146 | + <button |
| 147 | + v-if="isSupported" |
| 148 | + @click="handleCopy" |
| 149 | + class="text-muted-foreground absolute top-2 right-2 cursor-pointer text-[10px]" |
| 150 | + > |
| 151 | + <Icon icon="radix-icons:copy" class="mr-1 inline h-3 w-3" /> |
| 152 | + </button> |
| 153 | + <Transition name="fade-slide" mode="out-in"> |
| 154 | + <div :key="updates" class="font-mono leading-snug whitespace-pre-wrap select-text"> |
| 155 | + {{ preview }} |
| 156 | + </div> |
| 157 | + </Transition> |
| 158 | + </div> |
| 159 | + </div> |
| 160 | + </div> |
| 161 | + </DialogContent> |
| 162 | + </Dialog> |
| 163 | +</template> |
0 commit comments