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>
|
</template>
|
||||||
</div>
|
</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">
|
<div class="fc-bulk-panel__foot">
|
||||||
<v-btn variant="text" block @click="sel.clear()">Clear selection</v-btn>
|
<v-btn variant="text" block @click="sel.clear()">Clear selection</v-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<DestructiveConfirmModal
|
||||||
|
v-model="deleteModalOpen"
|
||||||
|
action="delete"
|
||||||
|
kind="images-selection"
|
||||||
|
:run-id="bulkToken"
|
||||||
|
tier="C"
|
||||||
|
:projected-counts="bulkProjectedCounts"
|
||||||
|
:description="bulkDescription"
|
||||||
|
@confirm="onDeleteConfirm"
|
||||||
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useGallerySelectionStore } from '../../stores/gallerySelection.js'
|
import { useGallerySelectionStore } from '../../stores/gallerySelection.js'
|
||||||
|
import { useAdminStore } from '../../stores/admin.js'
|
||||||
import { useApi } from '../../composables/useApi.js'
|
import { useApi } from '../../composables/useApi.js'
|
||||||
|
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
||||||
|
|
||||||
const sel = useGallerySelectionStore()
|
const sel = useGallerySelectionStore()
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
const adminStore = useAdminStore()
|
||||||
|
|
||||||
const addModel = ref(null)
|
const addModel = ref(null)
|
||||||
const addHits = ref([])
|
const addHits = ref([])
|
||||||
@@ -99,6 +124,66 @@ async function onAddPick(id) {
|
|||||||
watch(() => sel.order.length, () => {
|
watch(() => sel.order.length, () => {
|
||||||
if (sel.isSelectMode) sel.refresh()
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user