Skip to content

Commit 5ba99ba

Browse files
Merge pull request #2022 from cgwire/player
[players] Annotation snapshot polish and player bug fixes
2 parents 50abbe5 + 01fd1cc commit 5ba99ba

13 files changed

Lines changed: 543 additions & 167 deletions

File tree

src/components/modals/AddAttachmentModal.vue

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,41 @@
2727
{{ $t('tasks.comment_image') }}
2828
</h1>
2929

30-
<div class="flexrow buttons attachment-modal-buttons">
30+
<div class="attachment-upload-zone">
3131
<file-upload
3232
ref="fileField"
33-
class="flexrow-item"
3433
:label="$t('main.select_file')"
3534
:accept="extensions"
3635
:multiple="true"
3736
:is-primary="false"
3837
@fileselected="onFileSelected"
3938
hide-file-names
4039
/>
41-
<p class="flexrow-item mt1" v-if="isMovie">
42-
{{ $t('main.or') }}
43-
</p>
44-
<p class="flexrow-item" v-if="isMovie">
45-
<button
46-
:class="{
47-
button: true,
48-
'is-loading': isAnnotationLoading
49-
}"
50-
@click="$emit('add-snapshots')"
51-
>
52-
{{ $t('main.attach_snapshots') }}
53-
</button>
54-
</p>
40+
</div>
41+
42+
<div class="snapshot-actions" v-if="isMovie || isPicture">
43+
<button
44+
:class="{
45+
button: true,
46+
'snapshot-button': true,
47+
'is-loading': snapshotLoading === 'standard'
48+
}"
49+
:disabled="snapshotLoading && snapshotLoading !== 'standard'"
50+
@click="$emit('add-snapshots')"
51+
>
52+
{{ $t('main.attach_snapshots') }}
53+
</button>
54+
<button
55+
:class="{
56+
button: true,
57+
'snapshot-button': true,
58+
'is-loading': snapshotLoading === 'label'
59+
}"
60+
:disabled="snapshotLoading && snapshotLoading !== 'label'"
61+
@click="$emit('add-snapshots-with-label')"
62+
>
63+
{{ $t('main.attach_snapshots_with_label') }}
64+
</button>
5565
</div>
5666

5767
<h3 class="subtitle has-text-centered" v-if="forms.length > 0">
@@ -118,16 +128,24 @@ const props = defineProps({
118128
isError: { type: Boolean, default: false },
119129
isLoading: { type: Boolean, default: false },
120130
isMovie: { type: Boolean, default: false },
131+
isPicture: { type: Boolean, default: false },
121132
title: { type: String, default: '' }
122133
})
123134
124-
const emit = defineEmits(['add-snapshots', 'cancel', 'confirm'])
135+
const emit = defineEmits([
136+
'add-snapshots',
137+
'add-snapshots-with-label',
138+
'cancel',
139+
'confirm'
140+
])
125141
126142
useModal(toRef(props, 'active'), emit)
127143
128144
const fileField = ref(null)
129145
const forms = ref([])
130-
const isAnnotationLoading = ref(false)
146+
// Tracks which snapshot button is currently extracting:
147+
// 'standard', 'label', or null when idle.
148+
const snapshotLoading = ref(null)
131149
const isDraggingFile = ref(false)
132150
133151
const onFileSelected = newForms => {
@@ -195,11 +213,11 @@ onBeforeUnmount(() => {
195213
196214
defineExpose({
197215
addFiles,
198-
showAnnotationLoading: () => {
199-
isAnnotationLoading.value = true
216+
showAnnotationLoading: (kind = 'standard') => {
217+
snapshotLoading.value = kind
200218
},
201219
hideAnnotationLoading: () => {
202-
isAnnotationLoading.value = false
220+
snapshotLoading.value = null
203221
}
204222
})
205223
</script>
@@ -264,6 +282,57 @@ h3.subtitle {
264282
flex-wrap: wrap;
265283
}
266284
285+
.attachment-upload-zone {
286+
border: 2px dashed var(--border);
287+
border-radius: 10px;
288+
margin: 1em 0;
289+
padding: 1.5em;
290+
text-align: center;
291+
transition:
292+
border-color 0.2s ease,
293+
background 0.2s ease;
294+
295+
&:hover {
296+
background: var(--background-hover, rgba(255, 255, 255, 0.03));
297+
border-color: var(--background-selectable, $purple-strong);
298+
}
299+
300+
// Strip Bulma's button background off the file-upload label so the
301+
// dropzone's outline is the single visual container — the text sits
302+
// directly on the dashed area, no inner button chip.
303+
:deep(.dropbox) {
304+
background: transparent;
305+
border: 0;
306+
justify-content: center;
307+
padding: 0;
308+
}
309+
310+
:deep(.dropbox label.button) {
311+
background: transparent;
312+
border: 0;
313+
color: var(--text);
314+
cursor: pointer;
315+
font-weight: 500;
316+
317+
&:hover {
318+
background: transparent;
319+
color: var(--background-selectable, $purple-strong);
320+
}
321+
}
322+
}
323+
324+
.snapshot-actions {
325+
display: flex;
326+
flex-direction: column;
327+
gap: 0.5em;
328+
margin-bottom: 1em;
329+
}
330+
331+
.snapshot-button {
332+
width: 100%;
333+
margin-left: 0;
334+
}
335+
267336
.drop-mask {
268337
align-items: center;
269338
background: rgba(0.1, 0, 0, 0.5);

src/components/pages/Task.vue

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@
257257
:is-max-retakes-error="errors.addCommentMaxRetakes"
258258
:is-loading="loading.addComment"
259259
:is-movie="isMovie"
260+
:is-picture="isPicture"
260261
:team="currentTeam"
261262
:task-types="currentTaskTypes"
262263
:task="task"
@@ -269,6 +270,9 @@
269270
@file-drop="selectFile"
270271
@clear-files="clearPreviewFiles"
271272
@annotation-snapshots-requested="extractAnnotationSnapshots"
273+
@annotation-snapshots-with-label-requested="
274+
extractAnnotationSnapshots(true)
275+
"
272276
@remove-preview="onPreviewFormRemoved"
273277
v-if="isCommentingAllowed"
274278
/>
@@ -630,6 +634,10 @@ export default {
630634
return this.extension === 'mp4'
631635
},
632636
637+
isPicture() {
638+
return ['png', 'gif'].includes(this.extension)
639+
},
640+
633641
isPreviewPlayerReadOnly() {
634642
if (this.task) {
635643
if (this.isCurrentUserManager || this.isCurrentUserClient) {
@@ -1543,10 +1551,13 @@ export default {
15431551
this.taskPreviews = this.getCurrentTaskPreviews()
15441552
},
15451553
1546-
async extractAnnotationSnapshots() {
1547-
this.$refs['add-comment'].showAnnotationLoading()
1548-
const files =
1549-
await this.$refs['preview-player'].extractAnnotationSnapshots()
1554+
async extractAnnotationSnapshots(withLabel = false) {
1555+
this.$refs['add-comment'].showAnnotationLoading(
1556+
withLabel ? 'label' : 'standard'
1557+
)
1558+
const files = await this.$refs[
1559+
'preview-player'
1560+
].extractAnnotationSnapshots({ withLabel })
15501561
this.$refs['add-comment'].setAnnotationSnapshots(files)
15511562
this.$refs['add-comment'].hideAnnotationLoading()
15521563
return files

src/components/players/annotations/AnnotationCanvas.vue

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ const updateBounds = () => {
9090
canvas.setDimensions({ width: mediaRect.width, height: mediaRect.height })
9191
emit('resized', { width: mediaRect.width, height: mediaRect.height })
9292
}
93+
// Refresh fabric's cached canvas offset. setDimensions doesn't
94+
// always trigger it on its own, and a stale offset shifts pointer
95+
// coordinates (so freshly drawn strokes land at the wrong y) until
96+
// any window resize forces a recalculation.
97+
canvas?.calcOffset()
9398
}
9499
95100
const observe = el => el && resizeObserver?.observe(el)
@@ -202,12 +207,11 @@ defineExpose({
202207
.annotation-clip {
203208
position: absolute;
204209
z-index: 500;
205-
// Clip on all sides so a zoomed / panned overlay stays within the
206-
// media bounds — otherwise strokes spill onto the header above and
207-
// the playback / progress bars below.
208-
clip-path: inset(0);
209-
// The wrapper exists only to clip — never to capture events. The
210-
// inner overlay decides for itself whether to be interactive.
210+
// Clipping is delegated to the player's outer container
211+
// (PreviewPlayer's .preview-container, PlaylistPlayer's
212+
// .video-container — both with overflow: hidden) so the overlay
213+
// can extend with the panzoom transform without being cropped to
214+
// the media's un-zoomed bounds.
211215
pointer-events: none;
212216
}
213217

src/components/players/bars/PlayerPlaybackBar.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
<div class="left flexrow" v-if="isMovie && !compact">
3232
<span
33-
class="flexrow-item time-indicator"
33+
class="flexrow-item time-indicator current-time"
3434
:title="$t('playlists.actions.current_time')"
3535
>
3636
{{ currentTime }}
@@ -194,7 +194,15 @@ const volume = defineModel('volume', { default: 50 })
194194
<style lang="scss" scoped>
195195
.time-indicator {
196196
color: $light-grey;
197-
padding-left: 0.8em;
197+
padding-left: 0.2em;
198198
margin-right: 0;
199199
}
200+
201+
// Hide the timecode when PreviewPlayer is narrow — the frame counter
202+
// next to it carries enough position info on its own.
203+
@container preview-player (max-width: 600px) {
204+
.current-time {
205+
display: none;
206+
}
207+
}
200208
</style>

0 commit comments

Comments
 (0)