feat(scan): support iX1600 duplex scanning on Linux
This commit is contained in:
@@ -22,6 +22,8 @@ const agents = ref([])
|
||||
const selectedAgentId = ref(null)
|
||||
const currentJob = ref(null)
|
||||
const statusMessage = ref("")
|
||||
let disposed = false
|
||||
onBeforeUnmount(() => { disposed = true })
|
||||
|
||||
const scanForm = reactive({
|
||||
filename: `scan-${new Date().toISOString().slice(0, 10)}.pdf`,
|
||||
@@ -30,10 +32,27 @@ const scanForm = reactive({
|
||||
resolution: 300,
|
||||
mode: "Color",
|
||||
source: "ADF Duplex",
|
||||
postprocess: true,
|
||||
postprocessProfile: "receipt"
|
||||
paperSize: "A4",
|
||||
postprocess: false,
|
||||
postprocessProfile: "document"
|
||||
})
|
||||
|
||||
const isFeederScan = computed(() => /adf|document feeder/i.test(scanForm.source || ""))
|
||||
const formatOptions = computed(() => isFeederScan.value
|
||||
? [{ label: "PDF (alle Seiten)", value: "pdf" }]
|
||||
: [{ label: "PDF", value: "pdf" }, { label: "PNG", value: "png" }, { label: "TIFF", value: "tiff" }])
|
||||
watch(isFeederScan, (feeder) => { if (feeder) scanForm.format = "pdf" })
|
||||
watch(() => scanForm.format, (format) => {
|
||||
scanForm.filename = `${scanForm.filename.replace(/\.(pdf|png|tiff?)$/i, "")}.${format}`
|
||||
})
|
||||
|
||||
const applyIx1600Preset = () => {
|
||||
Object.assign(scanForm, {
|
||||
format: "pdf", resolution: 300, mode: "Color", source: "ADF Duplex",
|
||||
paperSize: "A4", postprocess: false, postprocessProfile: "document"
|
||||
})
|
||||
}
|
||||
|
||||
const activeAgents = computed(() =>
|
||||
agents.value.filter((agent) => agent.active && agent.capabilities?.scan)
|
||||
)
|
||||
@@ -62,8 +81,10 @@ const applyAgentDefaults = (agent) => {
|
||||
scanForm.resolution = Number(agent.scanDefaults?.resolution || 300)
|
||||
scanForm.mode = agent.scanDefaults?.mode || "Color"
|
||||
scanForm.source = agent.scanDefaults?.source || "ADF Duplex"
|
||||
scanForm.postprocess = agent.scanDefaults?.postprocess !== false
|
||||
scanForm.postprocessProfile = agent.scanDefaults?.postprocessProfile || "receipt"
|
||||
scanForm.paperSize = agent.scanDefaults?.paperSize || "A4"
|
||||
scanForm.postprocess = Boolean(agent.scanDefaults?.postprocess)
|
||||
scanForm.postprocessProfile = agent.scanDefaults?.postprocessProfile || "document"
|
||||
if (isFeederScan.value) scanForm.format = "pdf"
|
||||
|
||||
if (!scanForm.filename || scanForm.filename.startsWith("scan-")) {
|
||||
scanForm.filename = `scan-${new Date().toISOString().slice(0, 10)}.${scanForm.format || "pdf"}`
|
||||
@@ -94,10 +115,19 @@ const loadAgents = async () => {
|
||||
}
|
||||
|
||||
const waitForJob = async (jobId) => {
|
||||
for (let i = 0; i < 90; i++) {
|
||||
while (!disposed) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
if (disposed) return null
|
||||
|
||||
const response = await $api(`/api/scan-jobs/${jobId}`)
|
||||
let response
|
||||
try {
|
||||
response = await $api(`/api/scan-jobs/${jobId}`, { timeout: 15000, retry: 0 })
|
||||
} catch (err) {
|
||||
if ([401, 403, 404].includes(err?.statusCode || err?.response?.status)) throw err
|
||||
statusMessage.value = "Verbindung unterbrochen. Der Scan läuft auf dem Rechner weiter; Status wird erneut abgefragt..."
|
||||
continue
|
||||
}
|
||||
if (disposed) return null
|
||||
const job = response?.job
|
||||
currentJob.value = job
|
||||
|
||||
@@ -106,16 +136,15 @@ const waitForJob = async (jobId) => {
|
||||
throw new Error(job.agentMessage || "Scan-Auftrag wurde abgebrochen.")
|
||||
}
|
||||
|
||||
statusMessage.value = job?.status === "running"
|
||||
statusMessage.value = job?.agentMessage || (job?.status === "running"
|
||||
? "Scanner arbeitet..."
|
||||
: "Warte auf den Agenten..."
|
||||
: "Warte auf den Agenten...")
|
||||
}
|
||||
|
||||
throw new Error("Der Scan-Auftrag läuft länger als erwartet.")
|
||||
return null
|
||||
}
|
||||
|
||||
const startScan = async () => {
|
||||
if (!selectedAgentId.value) return
|
||||
if (!selectedAgentId.value || scanInProgress.value) return
|
||||
|
||||
scanInProgress.value = true
|
||||
statusMessage.value = "Scan-Auftrag wird erstellt..."
|
||||
@@ -133,6 +162,7 @@ const startScan = async () => {
|
||||
resolution: Number(scanForm.resolution || 300),
|
||||
mode: scanForm.mode || "Color",
|
||||
source: scanForm.source || null,
|
||||
paperSize: scanForm.paperSize,
|
||||
postprocess: scanForm.postprocess,
|
||||
postprocessProfile: scanForm.postprocessProfile
|
||||
},
|
||||
@@ -150,17 +180,19 @@ const startScan = async () => {
|
||||
|
||||
statusMessage.value = "Warte auf den Agenten..."
|
||||
|
||||
await waitForJob(response.job.id)
|
||||
const finishedJob = await waitForJob(response.job.id)
|
||||
if (!finishedJob || disposed) return
|
||||
|
||||
toast.add({
|
||||
title: "Scan gespeichert",
|
||||
description: props.scanData.folder ? "Die Datei wurde im aktuellen Ordner abgelegt." : "Die Datei wurde in Dateien abgelegt.",
|
||||
description: finishedJob.agentMessage || (props.scanData.folder ? "Die Datei wurde im aktuellen Ordner abgelegt." : "Die Datei wurde in Dateien abgelegt."),
|
||||
color: "success"
|
||||
})
|
||||
|
||||
emit("scanFinished")
|
||||
modal.close()
|
||||
} catch (err) {
|
||||
if (disposed) return
|
||||
toast.add({
|
||||
title: "Scan fehlgeschlagen",
|
||||
description: err?.data?.error || err?.message || "Unbekannter Fehler",
|
||||
@@ -246,6 +278,10 @@ loadAgents()
|
||||
<UInput v-model="scanForm.filename" :disabled="scanInProgress" />
|
||||
</UFormField>
|
||||
|
||||
<UButton color="neutral" variant="soft" :disabled="scanInProgress" @click="applyIx1600Preset">
|
||||
ScanSnap iX1600: A4, Duplex, PDF
|
||||
</UButton>
|
||||
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<UFormField label="Quelle">
|
||||
<UInput v-model="scanForm.source" placeholder="ADF Duplex" :disabled="scanInProgress" />
|
||||
@@ -255,6 +291,17 @@ loadAgents()
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<UFormField label="Papierformat">
|
||||
<USelectMenu v-model="scanForm.paperSize" :items="[
|
||||
{ label: 'A4', value: 'A4' }, { label: 'Letter', value: 'Letter' },
|
||||
{ label: 'Scanner-Vorgabe', value: 'auto' }
|
||||
]" value-key="value" label-key="label" :disabled="scanInProgress" />
|
||||
</UFormField>
|
||||
|
||||
<p v-if="isFeederScan" class="text-xs text-muted">
|
||||
Der gesamte Stapel wird als ein PDF gespeichert. Bei Duplex bleiben Vorder- und Rückseiten einschließlich Leerseiten erhalten.
|
||||
</p>
|
||||
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<UFormField label="Modus">
|
||||
<UInput v-model="scanForm.mode" placeholder="Color" :disabled="scanInProgress" />
|
||||
@@ -262,11 +309,7 @@ loadAgents()
|
||||
<UFormField label="Format">
|
||||
<USelectMenu
|
||||
v-model="scanForm.format"
|
||||
:items="[
|
||||
{ label: 'PDF', value: 'pdf' },
|
||||
{ label: 'PNG', value: 'png' },
|
||||
{ label: 'TIFF', value: 'tiff' }
|
||||
]"
|
||||
:items="formatOptions"
|
||||
value-key="value"
|
||||
label-key="label"
|
||||
:disabled="scanInProgress"
|
||||
@@ -307,7 +350,9 @@ loadAgents()
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<UButton color="neutral" variant="ghost" :disabled="scanInProgress" @click="modal.close()">Abbrechen</UButton>
|
||||
<UButton color="neutral" variant="ghost" @click="modal.close()">
|
||||
{{ scanInProgress ? 'Im Hintergrund weiterlaufen lassen' : 'Schließen' }}
|
||||
</UButton>
|
||||
<UButton
|
||||
icon="i-heroicons-document-magnifying-glass"
|
||||
:loading="scanInProgress"
|
||||
|
||||
Reference in New Issue
Block a user