Skip to content

Commit 5db6d1a

Browse files
[feat] Add video help dialog to Upload Model flow (#7177)
## Summary Adds an interactive video tutorial dialog to help users find CivitAI model URLs during the Upload Model wizard. ## Changes - **New Component**: Created reusable `VideoHelpDialog.vue` component - Full-width video player with floating close button - Configurable props: `videoUrl`, `loop`, `showControls` - Custom ESC key handling to prevent parent dialog from closing - Click backdrop to dismiss - 70% dark backdrop for better video focus - **Upload Model Flow**: Integrated video help button in step 1 footer - "How do I find this?" button opens tutorial video - Video demonstrates finding model URLs on CivitAI - PostHog tracking attribute maintained (`upload-model-step1-help-link`) ## Review Focus - ESC key event handling uses capture phase to prevent propagation to parent dialogs - Component follows existing patterns from `MediaVideoTop.vue` and `BaseModalLayout.vue` - Video player accessibility (ARIA labels, keyboard navigation) 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7177-feat-Add-video-help-dialog-to-Upload-Model-flow-2c06d73d36508148963ad9ee60038ea3) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude <[email protected]>
1 parent 4bf766d commit 5db6d1a

File tree

3 files changed

+98
-11
lines changed

3 files changed

+98
-11
lines changed

src/locales/en/main.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2113,6 +2113,8 @@
21132113
"uploadingModel": "Importing model...",
21142114
"uploadSuccess": "Model imported successfully!",
21152115
"uploadFailed": "Import failed",
2116+
"uploadModelHelpVideo": "Upload Model Help Video",
2117+
"uploadModelHowDoIFindThis": "How do I find this?",
21162118
"modelAssociatedWithLink": "The model associated with the link you provided:",
21172119
"modelTypeSelectorLabel": "What type of model is this?",
21182120
"modelTypeSelectorPlaceholder": "Select model type",

src/platform/assets/components/UploadModelFooter.vue

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<template>
22
<div class="flex justify-end gap-2 w-full">
3-
<span
3+
<IconTextButton
44
v-if="currentStep === 1"
5-
class="text-muted-foreground mr-auto underline flex items-center gap-2"
5+
:label="$t('assetBrowser.uploadModelHowDoIFindThis')"
6+
type="transparent"
7+
size="md"
8+
class="mr-auto underline text-muted-foreground"
9+
data-attr="upload-model-step1-help-link"
10+
@click="showVideoHelp = true"
611
>
7-
<i class="icon-[lucide--circle-question-mark]" />
8-
<a
9-
href="#"
10-
target="_blank"
11-
class="text-muted-foreground"
12-
data-attr="upload-model-step1-help-link"
13-
>{{ $t('How do I find this?') }}</a
14-
>
15-
</span>
12+
<template #icon>
13+
<i class="icon-[lucide--circle-question-mark]" />
14+
</template>
15+
</IconTextButton>
1616
<TextButton
1717
v-if="currentStep === 1"
1818
:label="$t('g.cancel')"
@@ -73,12 +73,22 @@
7373
data-attr="upload-model-step3-finish-button"
7474
@click="emit('close')"
7575
/>
76+
<VideoHelpDialog
77+
v-model="showVideoHelp"
78+
video-url="https://media.comfy.org/compressed_768/civitai_howto.webm"
79+
:aria-label="$t('assetBrowser.uploadModelHelpVideo')"
80+
/>
7681
</div>
7782
</template>
7883

7984
<script setup lang="ts">
85+
import { ref } from 'vue'
86+
8087
import IconTextButton from '@/components/button/IconTextButton.vue'
8188
import TextButton from '@/components/button/TextButton.vue'
89+
import VideoHelpDialog from '@/platform/assets/components/VideoHelpDialog.vue'
90+
91+
const showVideoHelp = ref(false)
8292
8393
defineProps<{
8494
currentStep: number
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<template>
2+
<Dialog
3+
v-model:visible="isVisible"
4+
modal
5+
:closable="false"
6+
:close-on-escape="false"
7+
:dismissable-mask="true"
8+
:pt="{
9+
root: { class: 'video-help-dialog' },
10+
header: { class: '!hidden' },
11+
content: { class: '!p-0' },
12+
mask: { class: '!bg-black/70' }
13+
}"
14+
:style="{ width: '90vw', maxWidth: '800px' }"
15+
>
16+
<div class="relative">
17+
<IconButton
18+
class="absolute top-4 right-6 z-10"
19+
:aria-label="$t('g.close')"
20+
@click="isVisible = false"
21+
>
22+
<i class="pi pi-times text-sm" />
23+
</IconButton>
24+
<video
25+
autoplay
26+
muted
27+
loop
28+
:aria-label="ariaLabel"
29+
class="w-full rounded-lg"
30+
:src="videoUrl"
31+
>
32+
{{ $t('g.videoFailedToLoad') }}
33+
</video>
34+
</div>
35+
</Dialog>
36+
</template>
37+
38+
<script setup lang="ts">
39+
import { useEventListener } from '@vueuse/core'
40+
import Dialog from 'primevue/dialog'
41+
import { onWatcherCleanup, watch } from 'vue'
42+
43+
import IconButton from '@/components/button/IconButton.vue'
44+
45+
const isVisible = defineModel<boolean>({ required: true })
46+
47+
const { videoUrl, ariaLabel = 'Help video' } = defineProps<{
48+
videoUrl: string
49+
ariaLabel?: string
50+
}>()
51+
52+
const handleEscapeKey = (event: KeyboardEvent) => {
53+
if (event.key === 'Escape') {
54+
event.stopImmediatePropagation()
55+
event.stopPropagation()
56+
event.preventDefault()
57+
isVisible.value = false
58+
}
59+
}
60+
61+
// Add listener with capture phase to intercept before parent dialogs
62+
// Only active when dialog is visible
63+
watch(
64+
isVisible,
65+
(visible) => {
66+
if (visible) {
67+
const stop = useEventListener(document, 'keydown', handleEscapeKey, {
68+
capture: true
69+
})
70+
onWatcherCleanup(stop)
71+
}
72+
},
73+
{ immediate: true }
74+
)
75+
</script>

0 commit comments

Comments
 (0)