981808b901
- Movie spec checks now also cover codec (h264/h265/mjpeg/dnxhd/dnxhr/
prores/uncompressed/avid/cineform) and frame rate (23.976/24/30/48/60),
detected in-browser via mediainfo.js container parsing - works even
for formats the native <video> element can't decode (MXF, ProRes,
DNxHD). Added .mxf to the supported/allowed format lists.
- Naming pattern supports a raw regex mode (validated by compiling
server-side at save time) as an alternative to the token pattern,
and gained three more tokens: {task_name}, {project_name},
{project_code} (fetched lazily only when referenced).
- Submission Configuration settings page now splits Shot Tasks /
Asset Tasks into separate tabs instead of one merged list; the
per-task-type card was extracted into SubmissionTypeConfigCard.vue
to avoid duplicating it across both tabs.
176 lines
5.9 KiB
Vue
176 lines
5.9 KiB
Vue
<template>
|
|
<div class="space-y-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h3 class="text-lg font-semibold">Submission Configuration</h3>
|
|
<p class="text-sm text-muted-foreground">Configure accepted file types and naming conventions artists must follow when submitting work, per task type</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="isLoading" class="text-center py-8 text-sm text-muted-foreground">
|
|
Loading submission configuration...
|
|
</div>
|
|
|
|
<Tabs v-else default-value="shot" class="w-full">
|
|
<TabsList class="grid w-full grid-cols-2">
|
|
<TabsTrigger value="shot">Shot Tasks</TabsTrigger>
|
|
<TabsTrigger value="asset">Asset Tasks</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="shot" class="space-y-4 mt-4">
|
|
<SubmissionTypeConfigCard
|
|
v-for="taskType in shotTaskTypes"
|
|
:key="taskType"
|
|
:task-type="taskType"
|
|
:config="getConfig(taskType)"
|
|
/>
|
|
<div v-if="shotTaskTypes.length === 0" class="text-center py-8 text-sm text-muted-foreground">
|
|
No shot task types configured for this project yet.
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="asset" class="space-y-4 mt-4">
|
|
<SubmissionTypeConfigCard
|
|
v-for="taskType in assetTaskTypes"
|
|
:key="taskType"
|
|
:task-type="taskType"
|
|
:config="getConfig(taskType)"
|
|
/>
|
|
<div v-if="assetTaskTypes.length === 0" class="text-center py-8 text-sm text-muted-foreground">
|
|
No asset task types configured for this project yet.
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
<div class="flex justify-end">
|
|
<Button :disabled="isLoading || isSaving" @click="onSave">
|
|
<div v-if="isSaving" class="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
|
Save Submission Configuration
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
import { useToast } from '@/components/ui/toast/use-toast'
|
|
import { projectService, type SubmissionTypeConfig } from '@/services/project'
|
|
import { customTaskTypeService } from '@/services/customTaskType'
|
|
import SubmissionTypeConfigCard from './SubmissionTypeConfigCard.vue'
|
|
|
|
interface Props {
|
|
projectId: number
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
const { toast } = useToast()
|
|
|
|
const isLoading = ref(false)
|
|
const isSaving = ref(false)
|
|
const shotTaskTypes = ref<string[]>([])
|
|
const assetTaskTypes = ref<string[]>([])
|
|
const formState = reactive<Record<string, SubmissionTypeConfig>>({})
|
|
|
|
const getConfig = (taskType: string): SubmissionTypeConfig => {
|
|
if (!formState[taskType]) {
|
|
formState[taskType] = {
|
|
allowed_extensions: [],
|
|
naming_pattern_is_regex: false,
|
|
naming_pattern: '',
|
|
check_naming: true,
|
|
required: false,
|
|
check_movie_spec: false,
|
|
movie_resolution: '',
|
|
movie_format: '',
|
|
movie_codec: '',
|
|
movie_frame_rate: null
|
|
}
|
|
}
|
|
return formState[taskType]
|
|
}
|
|
|
|
const load = async () => {
|
|
try {
|
|
isLoading.value = true
|
|
const [allTypes, config] = await Promise.all([
|
|
customTaskTypeService.getAllTaskTypes(props.projectId),
|
|
projectService.getProjectSubmissionConfig(props.projectId)
|
|
])
|
|
|
|
shotTaskTypes.value = allTypes.shot_task_types
|
|
assetTaskTypes.value = allTypes.asset_task_types
|
|
|
|
for (const [taskType, cfg] of Object.entries(config.config_by_task_type)) {
|
|
formState[taskType] = {
|
|
allowed_extensions: [...cfg.allowed_extensions],
|
|
naming_pattern_is_regex: cfg.naming_pattern_is_regex,
|
|
naming_pattern: cfg.naming_pattern || '',
|
|
check_naming: cfg.check_naming,
|
|
required: cfg.required,
|
|
check_movie_spec: cfg.check_movie_spec,
|
|
movie_resolution: cfg.movie_resolution || '',
|
|
movie_format: cfg.movie_format || '',
|
|
movie_codec: cfg.movie_codec || '',
|
|
movie_frame_rate: cfg.movie_frame_rate ?? null
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load submission configuration:', error)
|
|
toast({
|
|
title: 'Error',
|
|
description: 'Failed to load submission configuration',
|
|
variant: 'destructive'
|
|
})
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const onSave = async () => {
|
|
try {
|
|
isSaving.value = true
|
|
const configByTaskType: Record<string, SubmissionTypeConfig> = {}
|
|
for (const taskType of [...shotTaskTypes.value, ...assetTaskTypes.value]) {
|
|
const config = formState[taskType]
|
|
if (!config) continue
|
|
const hasConfig = config.allowed_extensions.length > 0 || !!config.naming_pattern
|
|
|| config.required || config.check_movie_spec
|
|
if (hasConfig) {
|
|
configByTaskType[taskType] = {
|
|
allowed_extensions: config.allowed_extensions,
|
|
naming_pattern_is_regex: config.naming_pattern_is_regex,
|
|
naming_pattern: config.naming_pattern || null,
|
|
check_naming: config.check_naming,
|
|
required: config.required,
|
|
check_movie_spec: config.check_movie_spec,
|
|
movie_resolution: config.movie_resolution || null,
|
|
movie_format: config.movie_format || null,
|
|
movie_codec: config.movie_codec || null,
|
|
movie_frame_rate: config.movie_frame_rate || null
|
|
}
|
|
}
|
|
}
|
|
|
|
await projectService.updateProjectSubmissionConfig(props.projectId, { config_by_task_type: configByTaskType })
|
|
|
|
toast({
|
|
title: 'Submission Configuration Updated',
|
|
description: 'Submission rules have been saved successfully'
|
|
})
|
|
} catch (error: any) {
|
|
console.error('Failed to save submission configuration:', error)
|
|
toast({
|
|
title: 'Error',
|
|
description: error.response?.data?.detail || 'Failed to save submission configuration',
|
|
variant: 'destructive'
|
|
})
|
|
} finally {
|
|
isSaving.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(load)
|
|
</script>
|