feat(stage-ui): new BugReporterDialog component
This commit is contained in:
@@ -68,6 +68,23 @@ dialogs:
|
||||
stateRequesting: Requesting...
|
||||
stateGranted: Granted
|
||||
stateNotGranted: Not granted
|
||||
bug-report:
|
||||
title: Bug report (´;ω;`)ヾ(・∀・`)
|
||||
subtitle: Oops, sorry we made something wrong. Would you mind telling us what happened?
|
||||
trigger-label: Report a Bug
|
||||
submit-label: Send Bug Report
|
||||
triage-description: Include screenshot and page context to help us triage this issue.
|
||||
description-placeholder: Steps to reproduce, expected behavior, and actual behavior
|
||||
include-current-page-screenshot: Include current page screenshot
|
||||
manual-media:
|
||||
label: Manually choose screenshots / recordings
|
||||
optional: Optional.
|
||||
media-files:
|
||||
label: Media files
|
||||
description: 'Optional: manually select.'
|
||||
placeholder: Choose screenshot or video file
|
||||
selected-count: '{count} file(s) selected'
|
||||
last-submitted-preview: Last Submitted Payload Preview
|
||||
language:
|
||||
title: Language
|
||||
description: |
|
||||
|
||||
@@ -45,3 +45,49 @@ notice:
|
||||
preparing: Preparing…
|
||||
read-more: Read more
|
||||
preview-title: What is it?
|
||||
|
||||
about:
|
||||
subtitle: Desktop ver.
|
||||
current-version: Current Version
|
||||
common:
|
||||
cancel: Cancel
|
||||
links:
|
||||
title: Links
|
||||
home: Home
|
||||
documentation: Documentations
|
||||
github: GitHub
|
||||
update:
|
||||
lane:
|
||||
label: Update lane
|
||||
description: Choose which release lane updater checks against. Auto follows the current app prerelease lane.
|
||||
placeholder: Choose update lane
|
||||
channels:
|
||||
auto: Auto
|
||||
stable: Stable
|
||||
alpha: Alpha
|
||||
beta: Beta
|
||||
nightly: Nightly
|
||||
canary: Canary
|
||||
status:
|
||||
downloading: Downloading update...
|
||||
error-prefix: Error
|
||||
latest: Up to date (v{version}).
|
||||
downloaded:
|
||||
windows: Update ready to install silently (v{version}).
|
||||
restart: Update ready to install on restart (v{version}).
|
||||
actions:
|
||||
download: Download Update
|
||||
download-short: Download
|
||||
confirm-download: Confirm Download
|
||||
check-for-updates: Check for updates
|
||||
checking: Checking...
|
||||
latest-version: Latest version
|
||||
disabled-dev: Updates disabled in Dev
|
||||
retry-check: Retry Check
|
||||
restart-silent: Restart to update silently
|
||||
restart-install: Restart to install update
|
||||
confirm-restart: Confirm Restart
|
||||
dialog:
|
||||
title: Update Available
|
||||
description: A new version (v{version}) is available.
|
||||
no-release-notes-markdown: _No release notes provided._
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
<script setup lang="ts">
|
||||
import type { BugReportPageContext } from './bug-report-payload'
|
||||
import type { BugReportDialogSubmitPayload } from './types'
|
||||
|
||||
import { useResizeObserver, useScreenSafeArea } from '@vueuse/core'
|
||||
import { DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, VisuallyHidden } from 'reka-ui'
|
||||
import { DrawerContent, DrawerHandle, DrawerOverlay, DrawerPortal, DrawerRoot } from 'vaul-vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import BugReportForm from './bug-report-form.vue'
|
||||
|
||||
import { useBreakpoints } from '../../../../composables/use-breakpoints'
|
||||
import { buildBugReportPayload, createBugReportPageContext } from './bug-report-payload'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
sending?: boolean
|
||||
submitError?: unknown
|
||||
title?: string
|
||||
subtitle?: string
|
||||
submitLabel?: string
|
||||
triageContextSummary?: string
|
||||
pageContext?: BugReportPageContext | null
|
||||
screenshotAttached?: boolean
|
||||
}>(), {
|
||||
sending: false,
|
||||
submitError: undefined,
|
||||
triageContextSummary: '',
|
||||
pageContext: null,
|
||||
screenshotAttached: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', payload: BugReportDialogSubmitPayload): void
|
||||
(e: 'requestTriageContext'): void
|
||||
(e: 'feedback'): void
|
||||
}>()
|
||||
|
||||
const showDialog = defineModel<boolean>({ default: false, required: false })
|
||||
const description = defineModel<string>('description', { default: '' })
|
||||
const includeTriageContext = defineModel<boolean>('includeTriageContext', { default: false })
|
||||
const uploadMediaFromLibrary = defineModel<boolean>('uploadMediaFromLibrary', { default: false })
|
||||
const screenshotFiles = defineModel<File[] | undefined>('screenshotFiles', { default: undefined })
|
||||
|
||||
const { t } = useI18n()
|
||||
const { isDesktop } = useBreakpoints()
|
||||
const screenSafeArea = useScreenSafeArea()
|
||||
|
||||
useResizeObserver(document.documentElement, () => screenSafeArea.update())
|
||||
onMounted(() => screenSafeArea.update())
|
||||
|
||||
const effectiveTriageSummary = computed(() => {
|
||||
if (props.triageContextSummary)
|
||||
return props.triageContextSummary
|
||||
|
||||
return t('settings.dialogs.bug-report.triage-description')
|
||||
})
|
||||
|
||||
const resolvedTitle = computed(() => props.title || t('settings.dialogs.bug-report.title'))
|
||||
const resolvedSubtitle = computed(() => props.subtitle || t('settings.dialogs.bug-report.subtitle'))
|
||||
const resolvedSubmitLabel = computed(() => props.submitLabel || t('settings.dialogs.bug-report.submit-label'))
|
||||
|
||||
function onSubmit() {
|
||||
const trimmedDescription = description.value.trim()
|
||||
if (!trimmedDescription)
|
||||
return
|
||||
|
||||
const includeContext = includeTriageContext.value
|
||||
const selectedScreenshotFiles = screenshotFiles.value ?? []
|
||||
const context = includeContext
|
||||
? (props.pageContext ?? createBugReportPageContext())
|
||||
: null
|
||||
const screenshotAttached = includeContext && (props.screenshotAttached || selectedScreenshotFiles.length > 0)
|
||||
const formattedReport = buildBugReportPayload({
|
||||
description: trimmedDescription,
|
||||
includeTriageContext: includeContext,
|
||||
context,
|
||||
screenshotAttached,
|
||||
})
|
||||
|
||||
emit('submit', {
|
||||
description: trimmedDescription,
|
||||
includeTriageContext: includeContext,
|
||||
context,
|
||||
screenshotAttached,
|
||||
screenshotFiles: selectedScreenshotFiles,
|
||||
formattedReport,
|
||||
})
|
||||
}
|
||||
|
||||
function onRequestTriageContext() {
|
||||
includeTriageContext.value = true
|
||||
emit('requestTriageContext')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogRoot v-if="isDesktop" :open="showDialog" @update:open="value => showDialog = value">
|
||||
<DialogPortal>
|
||||
<DialogOverlay
|
||||
:class="[
|
||||
'fixed inset-0 z-[9999] bg-black/50 backdrop-blur-sm',
|
||||
'data-[state=closed]:animate-fadeOut data-[state=open]:animate-fadeIn',
|
||||
]"
|
||||
/>
|
||||
<DialogContent
|
||||
:class="[
|
||||
'fixed left-1/2 top-1/2 z-[9999] max-h-full max-w-2xl w-[92dvw] transform',
|
||||
'flex flex-col overflow-hidden rounded-2xl bg-white p-6 shadow-xl outline-none',
|
||||
'backdrop-blur-md -translate-x-1/2 -translate-y-1/2',
|
||||
'data-[state=closed]:animate-contentHide data-[state=open]:animate-contentShow',
|
||||
'dark:bg-neutral-900',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'mb-4 flex flex-col gap-1',
|
||||
]"
|
||||
>
|
||||
<DialogTitle
|
||||
:class="[
|
||||
'text-lg font-semibold text-neutral-900 dark:text-neutral-100',
|
||||
]"
|
||||
>
|
||||
{{ resolvedTitle }}
|
||||
</DialogTitle>
|
||||
<DialogDescription
|
||||
:class="[
|
||||
'text-sm text-neutral-600 dark:text-neutral-300',
|
||||
]"
|
||||
>
|
||||
{{ resolvedSubtitle }}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
|
||||
<BugReportForm
|
||||
v-model:description="description"
|
||||
v-model:include-triage-context="includeTriageContext"
|
||||
v-model:upload-media-from-library="uploadMediaFromLibrary"
|
||||
v-model:screenshot-files="screenshotFiles"
|
||||
:sending="sending"
|
||||
:submit-error="submitError"
|
||||
:submit-label="resolvedSubmitLabel"
|
||||
:triage-context-summary="effectiveTriageSummary"
|
||||
@submit="onSubmit"
|
||||
@request-triage-context="onRequestTriageContext"
|
||||
@feedback="emit('feedback')"
|
||||
/>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</DialogRoot>
|
||||
|
||||
<DrawerRoot v-else :open="showDialog" should-scale-background @update:open="value => showDialog = value">
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay
|
||||
:class="[
|
||||
'fixed inset-0 z-1000 bg-black/35 backdrop-blur-sm',
|
||||
]"
|
||||
/>
|
||||
<DrawerContent
|
||||
:class="[
|
||||
'fixed bottom-0 left-0 right-0 z-1000 mt-20 h-full max-h-[90%]',
|
||||
'flex flex-col rounded-t-[32px] bg-neutral-50/95 px-4 pt-4 outline-none',
|
||||
'backdrop-blur-md dark:bg-neutral-900/95',
|
||||
]"
|
||||
:style="{ paddingBottom: `${Math.max(Number.parseFloat(screenSafeArea.bottom.value.replace('px', '')), 24)}px` }"
|
||||
>
|
||||
<VisuallyHidden>
|
||||
<DialogTitle>{{ resolvedTitle }}</DialogTitle>
|
||||
</VisuallyHidden>
|
||||
<DrawerHandle
|
||||
:class="[
|
||||
'[div&]:bg-neutral-400 [div&]:dark:bg-neutral-600',
|
||||
]"
|
||||
/>
|
||||
|
||||
<div
|
||||
:class="[
|
||||
'mb-4 mt-2 flex flex-col gap-1',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'text-lg font-semibold text-neutral-900 dark:text-neutral-100',
|
||||
]"
|
||||
>
|
||||
{{ resolvedTitle }}
|
||||
</div>
|
||||
<div
|
||||
:class="[
|
||||
'text-sm text-neutral-600 dark:text-neutral-300',
|
||||
]"
|
||||
>
|
||||
{{ resolvedSubtitle }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BugReportForm
|
||||
v-model:description="description"
|
||||
v-model:include-triage-context="includeTriageContext"
|
||||
v-model:upload-media-from-library="uploadMediaFromLibrary"
|
||||
v-model:screenshot-files="screenshotFiles"
|
||||
:sending="sending"
|
||||
:submit-error="submitError"
|
||||
:submit-label="resolvedSubmitLabel"
|
||||
:triage-context-summary="effectiveTriageSummary"
|
||||
@submit="onSubmit"
|
||||
@request-triage-context="onRequestTriageContext"
|
||||
@feedback="emit('feedback')"
|
||||
/>
|
||||
</DrawerContent>
|
||||
</DrawerPortal>
|
||||
</DrawerRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,130 @@
|
||||
<script setup lang="ts">
|
||||
import { Button, ContainerError, FieldCheckbox, FieldInputFile, Textarea } from '@proj-airi/ui'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
sending?: boolean
|
||||
submitError?: unknown
|
||||
submitLabel?: string
|
||||
triageContextSummary?: string
|
||||
}>(), {
|
||||
sending: false,
|
||||
submitError: undefined,
|
||||
triageContextSummary: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit'): void
|
||||
(e: 'requestTriageContext'): void
|
||||
(e: 'feedback'): void
|
||||
}>()
|
||||
|
||||
const description = defineModel<string>('description', { default: '' })
|
||||
const includeTriageContext = defineModel<boolean>('includeTriageContext', { default: false })
|
||||
const uploadMediaFromLibrary = defineModel<boolean>('uploadMediaFromLibrary', { default: false })
|
||||
const screenshotFiles = defineModel<File[] | undefined>('screenshotFiles', { default: undefined })
|
||||
|
||||
const { t } = useI18n()
|
||||
const canSubmit = computed(() => description.value.trim().length > 0 && !props.sending)
|
||||
const triageDescription = computed(() => props.triageContextSummary || t('settings.dialogs.bug-report.triage-description'))
|
||||
const resolvedSubmitLabel = computed(() => props.submitLabel || t('settings.dialogs.bug-report.submit-label'))
|
||||
const selectedScreenshotCount = computed(() => screenshotFiles.value?.length ?? 0)
|
||||
|
||||
function onIncludeTriageContextChange(next: boolean) {
|
||||
if (next) {
|
||||
emit('requestTriageContext')
|
||||
return
|
||||
}
|
||||
|
||||
uploadMediaFromLibrary.value = false
|
||||
screenshotFiles.value = undefined
|
||||
}
|
||||
|
||||
function onUploadMediaFromLibraryChange(next: boolean) {
|
||||
if (!next)
|
||||
screenshotFiles.value = undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'min-h-0 min-w-0 w-full flex flex-1 flex-col gap-4',
|
||||
]"
|
||||
>
|
||||
<Textarea
|
||||
v-model="description"
|
||||
:class="[
|
||||
'min-h-44 text-sm',
|
||||
]"
|
||||
:placeholder="t('settings.dialogs.bug-report.description-placeholder')"
|
||||
/>
|
||||
|
||||
<FieldCheckbox
|
||||
v-model="includeTriageContext"
|
||||
:label="t('settings.dialogs.bug-report.include-current-page-screenshot')"
|
||||
:description="triageDescription"
|
||||
@update:model-value="onIncludeTriageContextChange"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="includeTriageContext"
|
||||
:class="[
|
||||
'flex flex-col gap-2',
|
||||
]"
|
||||
>
|
||||
<FieldCheckbox
|
||||
v-model="uploadMediaFromLibrary"
|
||||
:label="t('settings.dialogs.bug-report.manual-media.label')"
|
||||
:description="t('settings.dialogs.bug-report.manual-media.optional')"
|
||||
@update:model-value="onUploadMediaFromLibraryChange"
|
||||
/>
|
||||
|
||||
<FieldInputFile
|
||||
v-if="uploadMediaFromLibrary"
|
||||
v-model="screenshotFiles"
|
||||
:label="t('settings.dialogs.bug-report.media-files.label')"
|
||||
:description="t('settings.dialogs.bug-report.media-files.description')"
|
||||
accept="image/*,video/*"
|
||||
multiple
|
||||
:placeholder="t('settings.dialogs.bug-report.media-files.placeholder')"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="uploadMediaFromLibrary && selectedScreenshotCount > 0"
|
||||
:class="[
|
||||
'text-xs text-neutral-500 dark:text-neutral-400',
|
||||
]"
|
||||
>
|
||||
{{ t('settings.dialogs.bug-report.media-files.selected-count', { count: selectedScreenshotCount }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
:class="[
|
||||
'min-h-0 flex-1',
|
||||
]"
|
||||
/>
|
||||
|
||||
<ContainerError
|
||||
v-if="submitError"
|
||||
:error="submitError"
|
||||
height-preset="sm"
|
||||
@feedback="emit('feedback')"
|
||||
/>
|
||||
|
||||
<Button
|
||||
:loading="sending"
|
||||
:disabled="!canSubmit"
|
||||
size="lg"
|
||||
block
|
||||
:class="[
|
||||
'min-h-14 text-base font-semibold',
|
||||
]"
|
||||
@click="emit('submit')"
|
||||
>
|
||||
{{ resolvedSubmitLabel }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { buildBugReportPayload, createBugReportPageContext } from './bug-report-payload'
|
||||
|
||||
describe('bug report payload helpers', () => {
|
||||
it('builds markdown payload with description and optional context/screenshot flags', () => {
|
||||
const payload = buildBugReportPayload({
|
||||
description: 'Clicking send does nothing',
|
||||
includeTriageContext: true,
|
||||
context: {
|
||||
url: 'https://airi.local/chat?room=debug',
|
||||
title: 'AIRI Chat',
|
||||
userAgent: 'test-agent',
|
||||
viewport: '1440x900',
|
||||
language: 'en-US',
|
||||
timeZone: 'Asia/Shanghai',
|
||||
timestamp: '2026-04-09T11:22:33.000Z',
|
||||
},
|
||||
screenshotAttached: true,
|
||||
})
|
||||
|
||||
expect(payload).toContain('## Bug Report')
|
||||
expect(payload).toContain('Clicking send does nothing')
|
||||
expect(payload).toContain('## Triage Context')
|
||||
expect(payload).toContain('- URL: https://airi.local/chat?room=debug')
|
||||
expect(payload).toContain('- Screenshot attached: yes')
|
||||
})
|
||||
|
||||
it('returns null page context when window is unavailable', () => {
|
||||
const context = createBugReportPageContext(undefined)
|
||||
expect(context).toBeNull()
|
||||
})
|
||||
|
||||
it('extracts page context from a window-like object', () => {
|
||||
const context = createBugReportPageContext({
|
||||
location: {
|
||||
href: 'https://airi.local/settings?tab=providers',
|
||||
},
|
||||
document: {
|
||||
title: 'Settings',
|
||||
},
|
||||
navigator: {
|
||||
userAgent: 'unit-test',
|
||||
language: 'en-US',
|
||||
},
|
||||
innerWidth: 1280,
|
||||
innerHeight: 720,
|
||||
Intl: {
|
||||
DateTimeFormat: () => ({
|
||||
resolvedOptions: () => ({ timeZone: 'UTC' }),
|
||||
}),
|
||||
},
|
||||
Date: {
|
||||
now: () => 1_700_000_000_000,
|
||||
},
|
||||
})
|
||||
|
||||
expect(context).toEqual({
|
||||
url: 'https://airi.local/settings?tab=providers',
|
||||
title: 'Settings',
|
||||
userAgent: 'unit-test',
|
||||
viewport: '1280x720',
|
||||
language: 'en-US',
|
||||
timeZone: 'UTC',
|
||||
timestamp: '2023-11-14T22:13:20.000Z',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,91 @@
|
||||
export interface BugReportPageContext {
|
||||
url: string
|
||||
title: string
|
||||
userAgent: string
|
||||
viewport: string
|
||||
language: string
|
||||
timeZone: string
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
export interface BuildBugReportPayloadOptions {
|
||||
description: string
|
||||
includeTriageContext?: boolean
|
||||
context?: BugReportPageContext | null
|
||||
screenshotAttached?: boolean
|
||||
}
|
||||
|
||||
interface WindowLike {
|
||||
location?: {
|
||||
href?: string
|
||||
}
|
||||
document?: {
|
||||
title?: string
|
||||
}
|
||||
navigator?: {
|
||||
userAgent?: string
|
||||
language?: string
|
||||
}
|
||||
innerWidth?: number
|
||||
innerHeight?: number
|
||||
Intl?: {
|
||||
DateTimeFormat?: () => {
|
||||
resolvedOptions?: () => {
|
||||
timeZone?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
Date?: {
|
||||
now?: () => number
|
||||
}
|
||||
}
|
||||
|
||||
export function createBugReportPageContext(win: WindowLike | undefined = globalThis.window): BugReportPageContext | null {
|
||||
if (!win)
|
||||
return null
|
||||
|
||||
const now = win.Date?.now?.() ?? Date.now()
|
||||
const timeZone = win.Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone ?? 'unknown'
|
||||
|
||||
return {
|
||||
url: win.location?.href ?? 'unknown',
|
||||
title: win.document?.title ?? '',
|
||||
userAgent: win.navigator?.userAgent ?? 'unknown',
|
||||
viewport: `${win.innerWidth ?? 0}x${win.innerHeight ?? 0}`,
|
||||
language: win.navigator?.language ?? 'unknown',
|
||||
timeZone,
|
||||
timestamp: new Date(now).toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
export function buildBugReportPayload(options: BuildBugReportPayloadOptions): string {
|
||||
const sections: string[] = [
|
||||
'## Bug Report',
|
||||
'',
|
||||
options.description.trim() || '_No description provided._',
|
||||
]
|
||||
|
||||
if (!options.includeTriageContext)
|
||||
return sections.join('\n')
|
||||
|
||||
sections.push('', '## Triage Context')
|
||||
|
||||
if (options.context) {
|
||||
sections.push(
|
||||
`- URL: ${options.context.url}`,
|
||||
`- Title: ${options.context.title || 'unknown'}`,
|
||||
`- Viewport: ${options.context.viewport}`,
|
||||
`- User Agent: ${options.context.userAgent}`,
|
||||
`- Language: ${options.context.language}`,
|
||||
`- Time Zone: ${options.context.timeZone}`,
|
||||
`- Captured At: ${options.context.timestamp}`,
|
||||
)
|
||||
}
|
||||
else {
|
||||
sections.push('- Page context unavailable')
|
||||
}
|
||||
|
||||
sections.push(`- Screenshot attached: ${options.screenshotAttached ? 'yes' : 'no'}`)
|
||||
|
||||
return sections.join('\n')
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<script setup lang="ts">
|
||||
import type { BugReportDialogSubmitPayload } from './types'
|
||||
|
||||
import { Button } from '@proj-airi/ui'
|
||||
import { computed, shallowRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import BugReportDialog from './bug-report-dialog.vue'
|
||||
|
||||
import { createBugReportPageContext } from './bug-report-payload'
|
||||
|
||||
const props = defineProps<{
|
||||
triggerLabel?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', payload: BugReportDialogSubmitPayload): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const showDialog = shallowRef(false)
|
||||
const description = shallowRef('')
|
||||
const includeTriageContext = shallowRef(false)
|
||||
const uploadMediaFromLibrary = shallowRef(false)
|
||||
const sending = shallowRef(false)
|
||||
const submitError = shallowRef<unknown>(undefined)
|
||||
const pageContext = shallowRef(createBugReportPageContext())
|
||||
const screenshotAttached = shallowRef(false)
|
||||
const screenshotFiles = shallowRef<File[] | undefined>(undefined)
|
||||
const submittedReport = shallowRef('')
|
||||
const resolvedTriggerLabel = computed(() => props.triggerLabel || t('settings.dialogs.bug-report.trigger-label'))
|
||||
|
||||
function onRequestTriageContext() {
|
||||
includeTriageContext.value = true
|
||||
screenshotAttached.value = true
|
||||
pageContext.value = createBugReportPageContext()
|
||||
}
|
||||
|
||||
function onSubmit(payload: BugReportDialogSubmitPayload) {
|
||||
sending.value = true
|
||||
submitError.value = undefined
|
||||
submittedReport.value = payload.formattedReport
|
||||
emit('submit', payload)
|
||||
|
||||
globalThis.setTimeout(() => {
|
||||
sending.value = false
|
||||
showDialog.value = false
|
||||
}, 250)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'flex flex-col gap-3',
|
||||
]"
|
||||
>
|
||||
<Button
|
||||
variant="danger"
|
||||
:class="[
|
||||
'w-fit',
|
||||
]"
|
||||
@click="showDialog = true"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'flex items-center gap-2',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'i-solar:danger-circle-outline h-4 w-4',
|
||||
]"
|
||||
/>
|
||||
{{ resolvedTriggerLabel }}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<div
|
||||
v-if="submittedReport"
|
||||
:class="[
|
||||
'rounded-lg border border-neutral-200/80 bg-neutral-50/80 p-3 text-xs',
|
||||
'text-neutral-600 leading-relaxed dark:border-neutral-700/60 dark:bg-neutral-900/50 dark:text-neutral-300',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'mb-2 text-xs font-semibold text-neutral-800 dark:text-neutral-200',
|
||||
]"
|
||||
>
|
||||
{{ t('settings.dialogs.bug-report.last-submitted-preview') }}
|
||||
</div>
|
||||
<pre
|
||||
:class="[
|
||||
'max-h-40 overflow-auto whitespace-pre-wrap break-words',
|
||||
]"
|
||||
>{{ submittedReport }}</pre>
|
||||
</div>
|
||||
|
||||
<BugReportDialog
|
||||
v-model="showDialog"
|
||||
v-model:description="description"
|
||||
v-model:include-triage-context="includeTriageContext"
|
||||
v-model:upload-media-from-library="uploadMediaFromLibrary"
|
||||
v-model:screenshot-files="screenshotFiles"
|
||||
:sending="sending"
|
||||
:submit-error="submitError"
|
||||
:page-context="pageContext"
|
||||
:screenshot-attached="screenshotAttached"
|
||||
@request-triage-context="onRequestTriageContext"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import BugReportTrigger from './bug-report-trigger.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story
|
||||
title="Dialogs / Bug Report"
|
||||
group="dialogs"
|
||||
>
|
||||
<Variant
|
||||
id="default-trigger"
|
||||
title="Default Trigger"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'mx-auto max-w-xl p-4',
|
||||
]"
|
||||
>
|
||||
<BugReportTrigger />
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="chat-themed-trigger"
|
||||
title="Chat Themed Trigger"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'mx-auto max-w-2xl rounded-2xl border border-neutral-200/70 bg-white/60 p-4',
|
||||
'dark:border-neutral-700/70 dark:bg-neutral-900/70',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'mb-3 text-sm text-neutral-600 dark:text-neutral-300',
|
||||
]"
|
||||
>
|
||||
Use this to test reporting from a chat-like screen.
|
||||
</div>
|
||||
<BugReportTrigger trigger-label="Report Chat Bug" />
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
@@ -0,0 +1,4 @@
|
||||
export { default as BugReportDialog } from './bug-report-dialog.vue'
|
||||
export * from './bug-report-payload'
|
||||
export { default as BugReportTrigger } from './bug-report-trigger.vue'
|
||||
export * from './types'
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { BugReportPageContext } from './bug-report-payload'
|
||||
|
||||
export interface BugReportDialogSubmitPayload {
|
||||
description: string
|
||||
includeTriageContext: boolean
|
||||
context: BugReportPageContext | null
|
||||
screenshotAttached: boolean
|
||||
screenshotFiles: File[]
|
||||
formattedReport: string
|
||||
}
|
||||
@@ -2,5 +2,6 @@ export * from './about'
|
||||
export { default as AboutDialogWithContent } from './about.vue'
|
||||
export * from './audio-input'
|
||||
export * from './background-picker'
|
||||
export * from './bug-report'
|
||||
export * from './onboarding'
|
||||
export * from './validation-details'
|
||||
|
||||
Reference in New Issue
Block a user