Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 218 additions & 30 deletions src/components/modals/ManageShotsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
:options="shotPaddingOptions"
class="shot-padding flexrow-item"
v-model="shotPadding"
v-show="shotMode === 'single'"
/>
</div>
</div>
Expand Down Expand Up @@ -104,39 +105,127 @@
</div>

<div class="shot-column">
<h2 class="subtitle">{{ $t('shots.title') }}</h2>
<div class="list">
<div
class="entity-line"
:key="shot.id"
v-for="shot in displayedShots"
<div class="shot-tabs">
<button
:class="['tab-button', { active: shotMode === 'single' }]"
@click="shotMode = 'single'"
>
{{ shot.name }}
</div>
</div>
<div class="field">
<input
class="input"
:placeholder="$t('shots.fields.placeholder')"
ref="addShotInput"
type="text"
@keyup.enter="addShot"
v-model="names.shot"
/>
</div>
<div class="field">
{{ $t('shots.single_tab') }}
</button>
<button
:class="{
button: true,
'is-success': true,
'is-loading': loading.addShot
}"
:disabled="!isAddShotAllowed || loading.addShot"
@click="addShot"
:class="['tab-button', { active: shotMode === 'bulk' }]"
@click="shotMode = 'bulk'"
>
{{ $t('main.add') }}
{{ $t('shots.bulk_tab') }}
</button>
</div>

<h2 class="subtitle">{{ $t('shots.title') }}</h2>

<div class="list">
<template v-if="shotMode === 'single'">
<div
class="entity-line"
:key="shot.id"
v-for="shot in displayedShots"
>
{{ shot.name }}
</div>
</template>
<template v-else>
<div
v-for="name in bulkPreviewNames"
:key="name"
:class="[
'entity-line',
{ collision: isBulkNameCollision(name) }
]"
>
{{ name }}
</div>
</template>
</div>

<template v-if="shotMode === 'single'">
<div class="field">
<input
class="input"
:placeholder="$t('shots.fields.placeholder')"
ref="addShotInput"
type="text"
@keyup.enter="addShot"
v-model="names.shot"
/>
</div>
<div class="field">
<button
:class="{
button: true,
'is-success': true,
'is-loading': loading.addShot
}"
:disabled="!isAddShotAllowed || loading.addShot"
@click="addShot"
>
{{ $t('main.add') }}
</button>
</div>
</template>

<template v-else>
<div class="field bulk-fields">
<div class="bulk-field">
<label class="label is-small">{{
$t('shots.fields.start')
}}</label>
<input
class="input"
type="text"
:placeholder="$t('shots.fields.placeholder')"
v-model="bulk.start"
/>
</div>
<div class="bulk-field">
<label class="label is-small">{{
$t('shots.fields.count')
}}</label>
<input
class="input"
type="number"
min="1"
max="500"
v-model.number="bulk.count"
/>
</div>
<div class="bulk-field">
<label class="label is-small">{{
$t('shots.fields.step')
}}</label>
<input
class="input"
type="number"
min="1"
v-model.number="bulk.step"
/>
</div>
</div>
<p v-if="bulkStartError" class="help is-danger">
{{ $t('shots.bulk_invalid_start') }}
</p>
<div class="field">
<button
:class="{
button: true,
'is-success': true,
'is-loading': loading.bulkGenerate
}"
:disabled="!isBulkGenerateAllowed"
@click="generateShots"
>
{{ $t('shots.bulk_generate') }}
</button>
</div>
</template>
</div>
</div>

Expand Down Expand Up @@ -170,7 +259,13 @@ const props = defineProps({
active: { type: Boolean, default: true }
})

const emit = defineEmits(['add-episode', 'add-sequence', 'add-shot', 'cancel'])
const emit = defineEmits([
'add-episode',
'add-sequence',
'add-shot',
'add-shots-bulk',
'cancel'
])

useModal(toRef(props, 'active'), emit)

Expand All @@ -188,13 +283,16 @@ const names = reactive({ episode: '', sequence: '', shot: '' })
const loading = reactive({
addEpisode: false,
addSequence: false,
addShot: false
addShot: false,
bulkGenerate: false
})
const sequences = ref([])
const displayedShots = ref([])
const selectedEpisodeId = ref(null)
const selectedSequenceId = ref(null)
const shotPadding = ref('1')
const shotMode = ref('single')
const bulk = reactive({ start: '', count: 20, step: 10 })

const currentProduction = computed(() => store.getters.currentProduction)
const displayedEpisodes = computed(() => store.getters.displayedEpisodes)
Expand All @@ -218,6 +316,26 @@ const isAddSequenceAllowed = computed(() => {
return !exists && (selectedEpisodeId.value || !isTVShow.value)
})

const bulkStartError = computed(
() => bulk.start.length > 0 && !/\d+$/.test(bulk.start)
)

const bulkPreviewNames = computed(() => {
if (!bulk.start || bulkStartError.value || !bulk.count || !bulk.step) {
return []
}
return stringHelpers.generateBulkShotNames(bulk.start, bulk.count, bulk.step)
})

const isBulkGenerateAllowed = computed(
() =>
!bulkStartError.value &&
bulkPreviewNames.value.length > 0 &&
!!selectedSequenceId.value &&
!bulkPreviewNames.value.some(name => isBulkNameCollision(name)) &&
!loading.bulkGenerate
)

const isAddShotAllowed = computed(() => {
if (!names.shot) return false
const exists = displayedShots.value.find(shot => names.shot === shot.name)
Expand Down Expand Up @@ -254,6 +372,33 @@ const selectEpisode = episodeId => {
}
}

const isBulkNameCollision = name =>
!!displayedShots.value.find(shot => shot.name === name)

const generateShots = () => {
if (!isBulkGenerateAllowed.value) return
loading.bulkGenerate = true
const sequence = displayedSequences.value.find(
s => s.id === selectedSequenceId.value
)
const episode = isTVShow.value
? displayedEpisodes.value.find(e => e.id === selectedEpisodeId.value)
: null
emit(
'add-shots-bulk',
{
shotNames: bulkPreviewNames.value,
sequenceName: sequence?.name,
episodeName: episode?.name ?? null
},
() => {
loading.bulkGenerate = false
selectSequence(selectedSequenceId.value)
bulk.start = ''
}
)
}

const addEpisode = () => {
if (!isAddEpisodeAllowed.value) return
loading.addEpisode = true
Expand Down Expand Up @@ -413,4 +558,47 @@ input::placeholder {
.shot-padding {
margin-right: 1em;
}

.shot-tabs {
display: flex;
margin-bottom: 4px;
}

.tab-button {
flex: 1;
border: 1px solid $light-grey;
background: transparent;
cursor: pointer;
padding: 4px 0;

&.active {
background: var(--background-selected);
}

&:first-child {
border-radius: 4px 0 0 4px;
}

&:last-child {
border-radius: 0 4px 4px 0;
border-left: 0;
}
}

.bulk-fields {
display: flex;
gap: 4px;
margin-right: 10px;
margin-bottom: 0;
}

.bulk-field {
flex: 1;
min-width: 0;
}

.entity-line.collision {
color: $red;
text-decoration: line-through;
}
</style>
14 changes: 14 additions & 0 deletions src/components/pages/Shots.vue
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
@add-episode="addEpisode"
@add-sequence="addSequence"
@add-shot="addShot"
@add-shots-bulk="addShotsBulk"
@cancel="hideManageShots"
/>

Expand Down Expand Up @@ -578,6 +579,7 @@ export default {
methods: {
...mapActions([
'addMetadataDescriptor',
'bulkCreateShots',
'createTasks',
'changeShotSort',
'clearSelectedShots',
Expand Down Expand Up @@ -651,6 +653,18 @@ export default {
this.newShot(shot).then(callback).catch(console.error)
},

addShotsBulk(payload, callback) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use callback. Use only promises.

this.bulkCreateShots(payload)
.then(() => this.loadShots())
.then(() => {
if (callback) callback()
})
.catch(err => {
console.error(err)
if (callback) callback()
})
},

onDeleteClicked(shot) {
this.shotToDelete = shot
this.modals.isDeleteDisplayed = true
Expand Down
16 changes: 16 additions & 0 deletions src/lib/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ export default {
}
},

generateBulkShotNames(startName, count, step) {
const matches = startName.match(/\d+$/)
if (!matches) return []
const numStr = matches[0]
const prefix = startName.slice(0, startName.length - numStr.length)
const padLen = numStr.length
const startNum = parseInt(numStr, 10)
const cappedCount = Math.min(count, 500)
const names = []
for (let i = 0; i < cappedCount; i++) {
const n = startNum + i * step
names.push(prefix + String(n).padStart(padLen, '0'))
}
return names
},

shortenText(text, maxLength) {
let result = text || ''
if (text?.length > maxLength) {
Expand Down
7 changes: 7 additions & 0 deletions src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1905,6 +1905,10 @@ export default {
manage: 'Create shots',
new_success: 'Shot {name} successfully created.',
padding: 'Shot Padding',
single_tab: 'Single',
bulk_tab: 'Bulk',
bulk_generate: 'Generate',
bulk_invalid_start: 'Start name must end with a number.',
restore_text: 'Are you sure you want to restore {name} from your archive?',
restore_error: 'An error occurred while restoring this shot.',
sequences: 'Sequences',
Expand All @@ -1924,6 +1928,9 @@ export default {
name: 'Name',
person: 'Modifier',
placeholder: 'SH01',
start: 'Start',
count: 'Count',
step: 'Step',
production: 'Prod',
resolution: 'Resolution',
sequence: 'Sequence',
Expand Down
Loading