fc3k(ui): bulk-delete action in BulkEditorPanel with sha8 confirm token + projected counts modal
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,19 +55,44 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="fc-bulk-panel__section">
|
||||
<h4>Destructive</h4>
|
||||
<v-btn
|
||||
color="error" variant="flat" rounded="pill" block
|
||||
prepend-icon="mdi-delete-forever"
|
||||
:disabled="!sel.count"
|
||||
:loading="deleting"
|
||||
@click="onDeleteClick"
|
||||
>Delete {{ sel.count }} selected</v-btn>
|
||||
</div>
|
||||
|
||||
<div class="fc-bulk-panel__foot">
|
||||
<v-btn variant="text" block @click="sel.clear()">Clear selection</v-btn>
|
||||
</div>
|
||||
|
||||
<DestructiveConfirmModal
|
||||
v-model="deleteModalOpen"
|
||||
action="delete"
|
||||
kind="images-selection"
|
||||
:run-id="bulkToken"
|
||||
tier="C"
|
||||
:projected-counts="bulkProjectedCounts"
|
||||
:description="bulkDescription"
|
||||
@confirm="onDeleteConfirm"
|
||||
/>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useGallerySelectionStore } from '../../stores/gallerySelection.js'
|
||||
import { useAdminStore } from '../../stores/admin.js'
|
||||
import { useApi } from '../../composables/useApi.js'
|
||||
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
||||
|
||||
const sel = useGallerySelectionStore()
|
||||
const api = useApi()
|
||||
const adminStore = useAdminStore()
|
||||
|
||||
const addModel = ref(null)
|
||||
const addHits = ref([])
|
||||
@@ -99,6 +124,66 @@ async function onAddPick(id) {
|
||||
watch(() => sel.order.length, () => {
|
||||
if (sel.isSelectMode) sel.refresh()
|
||||
})
|
||||
|
||||
// --- FC-3k bulk delete -----------------------------------------------
|
||||
|
||||
const deleting = ref(false)
|
||||
const deleteModalOpen = ref(false)
|
||||
const bulkProjected = ref(null)
|
||||
const bulkToken = ref('')
|
||||
|
||||
const bulkProjectedCounts = computed(() => bulkProjected.value
|
||||
? {
|
||||
images: bulkProjected.value.images_found,
|
||||
thumbnails: bulkProjected.value.thumbs_to_unlink,
|
||||
bytes: bulkProjected.value.bytes_on_disk,
|
||||
}
|
||||
: null,
|
||||
)
|
||||
const bulkDescription = computed(
|
||||
() => bulkProjected.value
|
||||
? `${bulkProjected.value.images_found} images, `
|
||||
+ `${Math.round(bulkProjected.value.bytes_on_disk / 1_048_576)} MiB on disk`
|
||||
: '',
|
||||
)
|
||||
|
||||
async function _computeSha8(ids) {
|
||||
const canon = [...ids].sort((a, b) => a - b).join(',')
|
||||
const buf = new TextEncoder().encode(canon)
|
||||
const hashBuf = await crypto.subtle.digest('SHA-256', buf)
|
||||
const bytes = new Uint8Array(hashBuf)
|
||||
let hex = ''
|
||||
for (let i = 0; i < 4; i++) {
|
||||
hex += bytes[i].toString(16).padStart(2, '0')
|
||||
}
|
||||
return hex
|
||||
}
|
||||
|
||||
async function onDeleteClick() {
|
||||
if (!sel.order.length) return
|
||||
deleting.value = true
|
||||
try {
|
||||
bulkProjected.value = await adminStore.projectBulkImageDelete(sel.order)
|
||||
bulkToken.value = await _computeSha8(sel.order)
|
||||
deleteModalOpen.value = true
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onDeleteConfirm(token) {
|
||||
deleting.value = true
|
||||
try {
|
||||
const result = await adminStore.dispatchBulkImageDelete(sel.order, token)
|
||||
const taskId = result.task_id
|
||||
if (taskId) {
|
||||
adminStore.pollTaskUntilDone(taskId).catch(() => {})
|
||||
}
|
||||
sel.clear()
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user