Fix Photos Add, Remove, and Replace against the library picker.
Remove clears the Photos array and backing image fields; Add opens the picker with disk upload and drops empty drafts on cancel. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,12 +4,14 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="slot-thumb"
|
class="slot-thumb"
|
||||||
:class="asset ? 'slot-thumb--slot' : 'slot-thumb--empty'"
|
:class="asset ? 'slot-thumb--slot' : 'slot-thumb--empty'"
|
||||||
:aria-label="asset ? 'Replace photo' : 'Select photo'"
|
:aria-label="asset ? 'Replace photo' : 'Choose a photo'"
|
||||||
@click="$emit('pick')"
|
@click="$emit('pick')"
|
||||||
>
|
>
|
||||||
<span v-if="asset && hidden" class="p-2 text-center text-[var(--type-sm)] text-[var(--muted)]">Still loaded · preview hidden</span>
|
<span v-if="asset && hidden" class="p-2 text-center text-[var(--type-sm)] text-[var(--muted)]">Still loaded · preview hidden</span>
|
||||||
<img v-else-if="asset" :src="thumbSrc" alt="">
|
<img v-else-if="asset" :src="thumbSrc" alt="">
|
||||||
<span v-else class="inline-flex items-center gap-1">Select <span aria-hidden="true">▾</span></span>
|
<span v-else class="inline-flex flex-col items-center gap-1 px-2 text-center">
|
||||||
|
<span>Choose a photo</span>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="photo-card-body">
|
<div class="photo-card-body">
|
||||||
<label class="photo-role-label">
|
<label class="photo-role-label">
|
||||||
@@ -31,6 +33,11 @@
|
|||||||
title="Mention this photo in the prompt"
|
title="Mention this photo in the prompt"
|
||||||
@click.stop="$emit('mention')"
|
@click.stop="$emit('mention')"
|
||||||
>Mention in prompt</button>
|
>Mention in prompt</button>
|
||||||
|
<button
|
||||||
|
v-if="!readonly && asset"
|
||||||
|
type="button"
|
||||||
|
@click.stop="$emit('pick')"
|
||||||
|
>Replace</button>
|
||||||
<button v-if="!readonly" type="button" @click.stop="$emit('remove')">Remove</button>
|
<button v-if="!readonly" type="button" @click.stop="$emit('remove')">Remove</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -356,10 +356,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="picker" class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-6" @click.self="closePicker">
|
<div v-if="picker" class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-6" @click.self="closePicker">
|
||||||
<section role="dialog" aria-modal="true" aria-label="Choose source asset" class="max-h-[80vh] w-full max-w-4xl overflow-auto rounded-[var(--radius)] border border-[var(--line)] bg-[var(--panel)] p-5">
|
<section role="dialog" aria-modal="true" aria-label="Choose a photo" class="max-h-[80vh] w-full max-w-4xl overflow-auto rounded-[var(--radius)] border border-[var(--line)] bg-[var(--panel)] p-5">
|
||||||
<div class="mb-4 flex justify-between"><h2>Choose a photo</h2><button type="button" @click="closePicker">Close</button></div>
|
<div class="mb-4 flex justify-between"><h2>Choose a photo</h2><button type="button" @click="closePicker">Cancel</button></div>
|
||||||
<div class="mb-4 flex flex-wrap items-center gap-3">
|
<div class="mb-4 flex flex-wrap items-center gap-3">
|
||||||
<button type="button" class="text-[var(--type-sm)] text-[var(--muted)]" @click="pickAsset(null)">Clear slot</button>
|
<label class="slot-upload">Upload from disk
|
||||||
|
<input type="file" accept="image/*" class="sr-only" :disabled="uploading" @change="uploadPickerFile">
|
||||||
|
</label>
|
||||||
<button
|
<button
|
||||||
v-if="xaigen && pickerAssets.length"
|
v-if="xaigen && pickerAssets.length"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -376,6 +378,7 @@
|
|||||||
<span class="mt-2 block truncate text-xs">{{ assetLabel(a) }}</span>
|
<span class="mt-2 block truncate text-xs">{{ assetLabel(a) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="!pickerAssets.length" class="text-[var(--type-sm)] text-[var(--muted)]">No stills in this folder yet. Upload from disk above.</p>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -663,21 +666,35 @@ function scrollFilmstripToNewest(){
|
|||||||
else if(strip)strip.scrollLeft=strip.scrollWidth
|
else if(strip)strip.scrollLeft=strip.scrollWidth
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function stillById(id:string){return stills.value.find((s:any)=>s.id===id)||null}
|
function stillById(id:string){return id?stills.value.find((s:any)=>s.id===id)||null:null}
|
||||||
|
/** Photos array is source of truth — never resurrect cards from leftover imageAId. */
|
||||||
function syncPhotosBindings(){
|
function syncPhotosBindings(){
|
||||||
const next=applyPhotosToRequest({...form,photos:form.photos})
|
const filled=(form.photos||[]).filter((p:any)=>p?.stillId)
|
||||||
form.photos=next.photos||[]
|
const next=applyPhotosToRequest({
|
||||||
|
...form,
|
||||||
|
photos:filled,
|
||||||
|
imageAId:'',
|
||||||
|
imageBId:'',
|
||||||
|
identityStillId:'',
|
||||||
|
})
|
||||||
|
// Keep empty draft cards in the UI; only overwrite filled bindings.
|
||||||
|
const drafts=(form.photos||[]).filter((p:any)=>!p?.stillId)
|
||||||
|
form.photos=[...(next.photos||[]),...drafts]
|
||||||
form.imageAId=next.imageAId||''
|
form.imageAId=next.imageAId||''
|
||||||
form.imageBId=next.imageBId||''
|
form.imageBId=next.imageBId||''
|
||||||
form.identityStillId=form.engine==='qwen21'?'':(next.identityStillId||'')
|
form.identityStillId=form.engine==='qwen21'?'':(next.identityStillId||'')
|
||||||
}
|
}
|
||||||
function addPhoto(){
|
function addPhoto(){
|
||||||
const plan=photosPlanState.value
|
const plan=photosPlanState.value
|
||||||
if(!plan.show||form.photos.length>=plan.max)return
|
if(!plan.show||plan.handoffReadonly)return
|
||||||
picker.value='photo-add'
|
if(form.photos.length>=plan.max)return
|
||||||
|
form.photos.push({stillId:'',role:plan.defaultRole||'change'})
|
||||||
|
picker.value='photo:'+(form.photos.length-1)
|
||||||
}
|
}
|
||||||
function removePhoto(index:number){
|
function removePhoto(index:number){
|
||||||
|
if(!Number.isInteger(index)||index<0||index>=form.photos.length)return
|
||||||
form.photos.splice(index,1)
|
form.photos.splice(index,1)
|
||||||
|
// Explicit empty photos array clears imageAId / imageBId / identityStillId.
|
||||||
syncPhotosBindings()
|
syncPhotosBindings()
|
||||||
}
|
}
|
||||||
function setPhotoRole(index:number,role:string){
|
function setPhotoRole(index:number,role:string){
|
||||||
@@ -685,6 +702,19 @@ function setPhotoRole(index:number,role:string){
|
|||||||
form.photos[index].role=role
|
form.photos[index].role=role
|
||||||
syncPhotosBindings()
|
syncPhotosBindings()
|
||||||
}
|
}
|
||||||
|
function setPhotoStill(index:number,stillId:string){
|
||||||
|
if(!Number.isInteger(index)||!form.photos[index])return
|
||||||
|
if(!stillId){
|
||||||
|
form.photos.splice(index,1)
|
||||||
|
}else{
|
||||||
|
form.photos[index].stillId=stillId
|
||||||
|
}
|
||||||
|
syncPhotosBindings()
|
||||||
|
}
|
||||||
|
function discardEmptyPhotoDraft(index:number){
|
||||||
|
if(!Number.isInteger(index)||!form.photos[index])return
|
||||||
|
if(!form.photos[index].stillId)form.photos.splice(index,1)
|
||||||
|
}
|
||||||
function capturePromptCaret(){
|
function capturePromptCaret(){
|
||||||
const el=promptArea.value
|
const el=promptArea.value
|
||||||
if(!el)return
|
if(!el)return
|
||||||
@@ -818,9 +848,69 @@ function toggleReveal(id:string){
|
|||||||
if(import.meta.client)localStorage.setItem(`studio2-revealed:${form.folderId}`,JSON.stringify([...next]))
|
if(import.meta.client)localStorage.setItem(`studio2-revealed:${form.folderId}`,JSON.stringify([...next]))
|
||||||
}
|
}
|
||||||
function closePicker(){
|
function closePicker(){
|
||||||
|
const key=picker.value
|
||||||
|
if(key.startsWith('photo:'))discardEmptyPhotoDraft(Number(key.slice(6)))
|
||||||
picker.value=''
|
picker.value=''
|
||||||
pickerRevealAll.value=false
|
pickerRevealAll.value=false
|
||||||
}
|
}
|
||||||
|
function pickAsset(asset:any){
|
||||||
|
const key=picker.value
|
||||||
|
if(key.startsWith('guide:'))form.guides[Number(key.split(':')[1])].stillId=asset?.id||''
|
||||||
|
else if(key==='startClipId')chooseClip(asset?.id||'')
|
||||||
|
else if(key.startsWith('photo:')){
|
||||||
|
const index=Number(key.slice(6))
|
||||||
|
if(asset?.id)setPhotoStill(index,asset.id)
|
||||||
|
else discardEmptyPhotoDraft(index)
|
||||||
|
}else if(key) form[key]=asset?.id||''
|
||||||
|
picker.value=''
|
||||||
|
pickerRevealAll.value=false
|
||||||
|
}
|
||||||
|
async function uploadPickerFile(event:Event){
|
||||||
|
const input=event.target as HTMLInputElement
|
||||||
|
const file=input.files?.[0]
|
||||||
|
if(!file)return
|
||||||
|
const key=picker.value
|
||||||
|
uploading.value=true;uploadMessage.value='';error.value=''
|
||||||
|
const body=new FormData();body.append('image',file);body.append('folderId',form.folderId);body.append('hideInput',String(xaigen))
|
||||||
|
try{
|
||||||
|
const response=await $fetch<any>('/api/studio-2/stills',{method:'POST',body})
|
||||||
|
library.value=response
|
||||||
|
measured[response.still.id]={width:response.still.width,height:response.still.height}
|
||||||
|
if(key.startsWith('photo:')){
|
||||||
|
setPhotoStill(Number(key.slice(6)),response.still.id)
|
||||||
|
picker.value=''
|
||||||
|
pickerRevealAll.value=false
|
||||||
|
}else{
|
||||||
|
Object.assign(form,applyUploadedStill(form,response,'source'))
|
||||||
|
}
|
||||||
|
uploadMessage.value=`Loaded ${response.still.name || file.name}.`
|
||||||
|
}catch(e:any){error.value=e.data?.statusMessage||e.message}
|
||||||
|
finally{uploading.value=false;input.value=''}
|
||||||
|
}
|
||||||
|
async function upload(event:Event,target:'source'|'hero'){
|
||||||
|
const input=event.target as HTMLInputElement,file=input.files?.[0];if(!file)return
|
||||||
|
uploading.value=true;uploadMessage.value='';error.value=''
|
||||||
|
const body=new FormData();body.append('image',file);body.append('folderId',form.folderId);body.append('hideInput',String(xaigen))
|
||||||
|
try{
|
||||||
|
const response=await $fetch<any>('/api/studio-2/stills',{method:'POST',body})
|
||||||
|
library.value=response
|
||||||
|
measured[response.still.id]={width:response.still.width,height:response.still.height}
|
||||||
|
const plan=photosPlanState.value
|
||||||
|
if(plan.show && !plan.handoffReadonly){
|
||||||
|
const role=target==='hero'
|
||||||
|
?(plan.roles.includes('person')?'person':plan.defaultRole)
|
||||||
|
:plan.defaultRole
|
||||||
|
if(form.photos.length>=plan.max)form.photos[form.photos.length-1]={stillId:response.still.id,role}
|
||||||
|
else form.photos.push({stillId:response.still.id,role:role||'change'})
|
||||||
|
syncPhotosBindings()
|
||||||
|
}else{
|
||||||
|
Object.assign(form,applyUploadedStill(form,response,target))
|
||||||
|
}
|
||||||
|
uploadMessage.value=`Loaded ${response.still.name || file.name}.`
|
||||||
|
}
|
||||||
|
catch(e:any){error.value=e.data?.statusMessage||e.message}
|
||||||
|
finally{uploading.value=false;input.value=''}
|
||||||
|
}
|
||||||
function loadRevealed(folderId:string){
|
function loadRevealed(folderId:string){
|
||||||
if(!import.meta.client||!xaigen){revealed.value=new Set();return}
|
if(!import.meta.client||!xaigen){revealed.value=new Set();return}
|
||||||
try{revealed.value=new Set(JSON.parse(localStorage.getItem(`studio2-revealed:${folderId}`)||'[]'))}
|
try{revealed.value=new Set(JSON.parse(localStorage.getItem(`studio2-revealed:${folderId}`)||'[]'))}
|
||||||
@@ -851,26 +941,6 @@ function selectOutput(item:any){selected.value=item;if(form.mode==='extend' && i
|
|||||||
function restore(item:any){Object.assign(form,restoreAll(form,item,xaigen,loraMemory));form.guides=form.guides||[];batchText.value=''}
|
function restore(item:any){Object.assign(form,restoreAll(form,item,xaigen,loraMemory));form.guides=form.guides||[];batchText.value=''}
|
||||||
function focusJob(job:any){selectOutput(job.outputs?.at(-1)||null);focusedJobId.value=job.id}
|
function focusJob(job:any){selectOutput(job.outputs?.at(-1)||null);focusedJobId.value=job.id}
|
||||||
async function loadLoras(fresh=false){try{loraNames.value=await $fetch('/api/studio-2/loras'+(fresh?'?fresh=1':''))}catch{error.value='Could not refresh LoRAs. The last loaded list is still available.'}}
|
async function loadLoras(fresh=false){try{loraNames.value=await $fetch('/api/studio-2/loras'+(fresh?'?fresh=1':''))}catch{error.value='Could not refresh LoRAs. The last loaded list is still available.'}}
|
||||||
function pickAsset(asset:any){
|
|
||||||
const key=picker.value
|
|
||||||
if(key.startsWith('guide:'))form.guides[Number(key.split(':')[1])].stillId=asset?.id||''
|
|
||||||
else if(key==='startClipId')chooseClip(asset?.id||'')
|
|
||||||
else if(key==='photo-add'){
|
|
||||||
if(asset?.id){
|
|
||||||
const plan=photosPlanState.value
|
|
||||||
form.photos.push({stillId:asset.id,role:plan.defaultRole||'change'})
|
|
||||||
syncPhotosBindings()
|
|
||||||
}
|
|
||||||
}else if(key.startsWith('photo:')){
|
|
||||||
const index=Number(key.slice(6))
|
|
||||||
if(Number.isInteger(index)&&form.photos[index]){
|
|
||||||
if(asset?.id)form.photos[index].stillId=asset.id
|
|
||||||
else form.photos.splice(index,1)
|
|
||||||
syncPhotosBindings()
|
|
||||||
}
|
|
||||||
}else if(key) form[key]=asset?.id||''
|
|
||||||
closePicker()
|
|
||||||
}
|
|
||||||
async function clearFinished(){await $fetch('/api/studio-2/clear-finished',{method:'POST'});await refresh()}
|
async function clearFinished(){await $fetch('/api/studio-2/clear-finished',{method:'POST'});await refresh()}
|
||||||
async function deleteOutput(){
|
async function deleteOutput(){
|
||||||
if(!selected.value)return
|
if(!selected.value)return
|
||||||
@@ -907,14 +977,6 @@ async function queue(){
|
|||||||
finally{submitting.value=false}
|
finally{submitting.value=false}
|
||||||
}
|
}
|
||||||
async function cancel(job:any){await $fetch('/api/studio-2/cancel',{method:'POST',body:{id:job.id}});await refresh()}
|
async function cancel(job:any){await $fetch('/api/studio-2/cancel',{method:'POST',body:{id:job.id}});await refresh()}
|
||||||
async function upload(event:Event,target:'source'|'hero'){
|
|
||||||
const input=event.target as HTMLInputElement,file=input.files?.[0];if(!file)return
|
|
||||||
uploading.value=true;uploadMessage.value='';error.value=''
|
|
||||||
const body=new FormData();body.append('image',file);body.append('folderId',form.folderId);body.append('hideInput',String(xaigen))
|
|
||||||
try{const response=await $fetch<any>('/api/studio-2/stills',{method:'POST',body});library.value=response;Object.assign(form,applyUploadedStill(form,response,target));measured[response.still.id]={width:response.still.width,height:response.still.height};uploadMessage.value=`Loaded ${response.still.name || file.name} as ${target==='hero'?'hero identity':'start still'}.`}
|
|
||||||
catch(e:any){error.value=e.data?.statusMessage||e.message}
|
|
||||||
finally{uploading.value=false;input.value=''}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function refresh(){
|
async function refresh(){
|
||||||
const [nextJobs,nextLibrary,nextUpscales,queue]=await Promise.all([
|
const [nextJobs,nextLibrary,nextUpscales,queue]=await Promise.all([
|
||||||
|
|||||||
@@ -301,6 +301,8 @@ export function photosFromRequest(request = {}) {
|
|||||||
/**
|
/**
|
||||||
* Apply Photos → request fields. Call before validate / queue.
|
* Apply Photos → request fields. Call before validate / queue.
|
||||||
* If Edit+Qwen has cards but no Photo to change, promote the first card.
|
* If Edit+Qwen has cards but no Photo to change, promote the first card.
|
||||||
|
* When `photos` is an explicit array (even empty), it is source of truth —
|
||||||
|
* do not resurrect cards from leftover imageAId / identityStillId.
|
||||||
*/
|
*/
|
||||||
export function applyPhotosToRequest(raw = {}) {
|
export function applyPhotosToRequest(raw = {}) {
|
||||||
const next = { ...raw }
|
const next = { ...raw }
|
||||||
@@ -309,8 +311,9 @@ export function applyPhotosToRequest(raw = {}) {
|
|||||||
if (!Array.isArray(next.photos)) next.photos = []
|
if (!Array.isArray(next.photos)) next.photos = []
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
const photosProvided = Array.isArray(raw.photos)
|
||||||
let photos = normalizePhotos(next.photos, plan)
|
let photos = normalizePhotos(next.photos, plan)
|
||||||
if (!photos.length && (next.imageAId || next.identityStillId || next.imageBId)) {
|
if (!photosProvided && !photos.length && (next.imageAId || next.identityStillId || next.imageBId)) {
|
||||||
photos = normalizePhotos(photosFromRequest(next), plan)
|
photos = normalizePhotos(photosFromRequest(next), plan)
|
||||||
}
|
}
|
||||||
// Promote first card to Photo to change when Edit needs it.
|
// Promote first card to Photo to change when Edit needs it.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {compilePrompt,validateRequest} from '../shared/studio2/contracts.mjs'
|
|||||||
import {cachedLoras} from '../shared/studio2/lora-cache.mjs'
|
import {cachedLoras} from '../shared/studio2/lora-cache.mjs'
|
||||||
import {resolveQwen21Size} from '../shared/studio2/qwen21-size.mjs'
|
import {resolveQwen21Size} from '../shared/studio2/qwen21-size.mjs'
|
||||||
import {ensureQwen21EditPrompt,stitchQwen21PeEditPrompt,QWEN21_EDIT_KEEP_CHANGE} from '../shared/studio2/qwen21-edit.mjs'
|
import {ensureQwen21EditPrompt,stitchQwen21PeEditPrompt,QWEN21_EDIT_KEEP_CHANGE} from '../shared/studio2/qwen21-edit.mjs'
|
||||||
import {injectQwenEditMentionTags} from '../shared/studio2/photos.mjs'
|
import {injectQwenEditMentionTags,applyPhotosToRequest} from '../shared/studio2/photos.mjs'
|
||||||
import {qwen21PeRefusal} from '../shared/studio2/qwen21-pe.mjs'
|
import {qwen21PeRefusal} from '../shared/studio2/qwen21-pe.mjs'
|
||||||
|
|
||||||
test('deleted library files never return from job outputs; selection falls back or clears',()=>{
|
test('deleted library files never return from job outputs; selection falls back or clears',()=>{
|
||||||
@@ -109,6 +109,11 @@ test('qwen21 supports Generate and Edit with 25/1 defaults and no locks',()=>{
|
|||||||
assert.equal(qwenPhotos.identityStillId,'')
|
assert.equal(qwenPhotos.identityStillId,'')
|
||||||
assert.match(qwenPhotos.compiledPrompt,/Put her in that dress/)
|
assert.match(qwenPhotos.compiledPrompt,/Put her in that dress/)
|
||||||
assert.doesNotMatch(String(qwenPhotos.promptSections.action),/<image1>/)
|
assert.doesNotMatch(String(qwenPhotos.promptSections.action),/<image1>/)
|
||||||
|
const afterRemove=applyPhotosToRequest({mode:'edit',engine:'qwen21',photos:[],imageAId:'stale-person',imageBId:'stale-dress'})
|
||||||
|
assert.equal(afterRemove.imageAId,'')
|
||||||
|
assert.equal(afterRemove.imageBId,'')
|
||||||
|
assert.deepEqual(afterRemove.photos,[])
|
||||||
|
assert.throws(()=>validateRequest({mode:'edit',engine:'qwen21',folderId:'f',photos:[],imageAId:'stale',promptSections:{action:'x'}}),/Photo to change/)
|
||||||
assert.throws(()=>validateRequest({mode:'compose',engine:'qwen21',folderId:'f',imageAId:'s',imageBId:'b',promptSections:{action:'x'}}),/Generate and Edit/)
|
assert.throws(()=>validateRequest({mode:'compose',engine:'qwen21',folderId:'f',imageAId:'s',imageBId:'b',promptSections:{action:'x'}}),/Generate and Edit/)
|
||||||
assert.throws(()=>validateRequest({mode:'iterate',engine:'qwen21',folderId:'f',imageAId:'s',promptSections:{action:'x'}}),/Generate and Edit/)
|
assert.throws(()=>validateRequest({mode:'iterate',engine:'qwen21',folderId:'f',imageAId:'s',promptSections:{action:'x'}}),/Generate and Edit/)
|
||||||
assert.equal(ensureQwen21EditPrompt('put her in a red leather jacket'),`${QWEN21_EDIT_KEEP_CHANGE} put her in a red leather jacket`)
|
assert.equal(ensureQwen21EditPrompt('put her in a red leather jacket'),`${QWEN21_EDIT_KEEP_CHANGE} put her in a red leather jacket`)
|
||||||
@@ -167,7 +172,10 @@ test('custom negative chips survive normalizeStyles and bench exposes Load actio
|
|||||||
assert.match(src,/photosPlanState\.emptyHint/)
|
assert.match(src,/photosPlanState\.emptyHint/)
|
||||||
assert.match(src,/aspect:'auto'/);assert.match(src,/strip-actions/);assert.match(src,/addNegative/)
|
assert.match(src,/aspect:'auto'/);assert.match(src,/strip-actions/);assert.match(src,/addNegative/)
|
||||||
assert.match(src,/Enhance prompt/);assert.match(src,/enhancePrompt/)
|
assert.match(src,/Enhance prompt/);assert.match(src,/enhancePrompt/)
|
||||||
assert.match(slot,/Mention in prompt/);assert.match(slot,/This photo is/)
|
assert.match(slot,/Mention in prompt/);assert.match(slot,/This photo is/);assert.match(slot,/Replace/)
|
||||||
|
assert.match(slot,/Choose a photo/)
|
||||||
|
assert.match(src,/uploadPickerFile/);assert.match(src,/setPhotoStill/);assert.match(src,/discardEmptyPhotoDraft/)
|
||||||
|
assert.match(src,/Upload from disk/)
|
||||||
assert.doesNotMatch(src,/Hero still/)
|
assert.doesNotMatch(src,/Hero still/)
|
||||||
assert.doesNotMatch(src,/Edit source/)
|
assert.doesNotMatch(src,/Edit source/)
|
||||||
assert.doesNotMatch(src,/Identity \/ reference/)
|
assert.doesNotMatch(src,/Identity \/ reference/)
|
||||||
|
|||||||
Reference in New Issue
Block a user