v1.1.2 dualside work, have bugs/need work
This commit is contained in:
@ -205,5 +205,5 @@
|
|||||||
|
|
||||||
<div class="version">
|
<div class="version">
|
||||||
<span>v{backendVersion}</span>
|
<span>v{backendVersion}</span>
|
||||||
<span>v1.1.0</span>
|
<span>v1.1.2</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
<!-- lib/pages/print/PrintProgress.svelte -->
|
|
||||||
<script>
|
<script>
|
||||||
let { store, onBack = () => {} } = $props();
|
let { store, onBack = () => {} } = $props();
|
||||||
|
|
||||||
@ -31,25 +30,14 @@
|
|||||||
|
|
||||||
const currentStatus = $derived.by(() => {
|
const currentStatus = $derived.by(() => {
|
||||||
if (!store.job) return 'idle';
|
if (!store.job) return 'idle';
|
||||||
|
if (jobFiles.some((f) => norm(f.status) === 'printing')) return 'printing';
|
||||||
if (jobFiles.some((f) => norm(f.status) === 'printing')) {
|
if (jobFiles.some((f) => norm(f.status) === 'queued')) return 'queued';
|
||||||
return 'printing';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jobFiles.some((f) => norm(f.status) === 'queued')) {
|
|
||||||
return 'queued';
|
|
||||||
}
|
|
||||||
|
|
||||||
return jobPhase;
|
return jobPhase;
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!store.jobActive) return;
|
if (!store.jobActive) return;
|
||||||
|
const int = setInterval(() => store.refreshJob(), 1000);
|
||||||
const int = setInterval(() => {
|
|
||||||
store.refreshJob();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => clearInterval(int);
|
return () => clearInterval(int);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -57,116 +45,68 @@
|
|||||||
jobFiles.some((f) => ['queued', 'printing'].includes(norm(f.status)))
|
jobFiles.some((f) => ['queued', 'printing'].includes(norm(f.status)))
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
// --- ЛОГИКА СОСТОЯНИЙ ---
|
||||||
* Явные состояния, при которых нужны пользовательские кнопки.
|
|
||||||
* Здесь больше не завязываемся на hasPrintedFiles / firstSideFinished.
|
const showClearOutput = $derived(
|
||||||
*/
|
store.jobActive &&
|
||||||
|
(jobPhase === 'awaiting_clear_output' || jobFiles.some(f => norm(f.status) === 'awaiting_clear_output'))
|
||||||
|
);
|
||||||
|
|
||||||
const showStart = $derived(
|
const showStart = $derived(
|
||||||
store.jobActive && jobPhase === 'waiting_start'
|
store.jobActive && jobPhase === 'waiting_start'
|
||||||
);
|
);
|
||||||
|
|
||||||
const showClearOutput = $derived(
|
|
||||||
store.jobActive &&
|
|
||||||
(
|
|
||||||
showStart ||
|
|
||||||
jobPhase === 'awaiting_clear_output' ||
|
|
||||||
jobFiles.some((f) => norm(f.status) === 'awaiting_clear_output')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const showFlip = $derived(
|
const showFlip = $derived(
|
||||||
store.jobActive &&
|
store.jobActive &&
|
||||||
(
|
(jobPhase === 'awaiting_flip' || jobFiles.some((f) => norm(f.status) === 'awaiting_flip')) &&
|
||||||
jobPhase === 'awaiting_flip' ||
|
jobPhase !== 'finished' && jobPhase !== 'cancelled'
|
||||||
jobFiles.some((f) => norm(f.status) === 'awaiting_flip')
|
|
||||||
) &&
|
|
||||||
jobPhase !== 'finished' &&
|
|
||||||
jobPhase !== 'cancelled'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const showPickup = $derived(
|
const showPickup = $derived(
|
||||||
store.jobActive &&
|
store.jobActive &&
|
||||||
(
|
(jobPhase === 'awaiting_pickup' || jobFiles.some((f) => norm(f.status) === 'awaiting_pickup')) &&
|
||||||
jobPhase === 'awaiting_pickup' ||
|
!showFlip && !showClearOutput &&
|
||||||
jobFiles.some((f) => norm(f.status) === 'awaiting_pickup')
|
jobPhase !== 'finished' && jobPhase !== 'cancelled'
|
||||||
) &&
|
|
||||||
!showFlip &&
|
|
||||||
jobPhase !== 'finished' &&
|
|
||||||
jobPhase !== 'cancelled'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearActionAvailable = $derived(showClearOutput);
|
const clearActionAvailable = $derived(showClearOutput);
|
||||||
|
const flipActionAvailable = $derived(showFlip && jobPhase === 'awaiting_flip');
|
||||||
|
const pickupActionAvailable = $derived(showPickup && jobPhase === 'awaiting_pickup');
|
||||||
|
|
||||||
const flipActionAvailable = $derived(
|
const clearButtonLabel = $derived('Лоток пуст — продолжить');
|
||||||
showFlip && jobPhase === 'awaiting_flip'
|
|
||||||
);
|
|
||||||
|
|
||||||
const pickupActionAvailable = $derived(
|
// Автостарт
|
||||||
showPickup && jobPhase === 'awaiting_pickup'
|
|
||||||
);
|
|
||||||
|
|
||||||
const clearButtonLabel = $derived(
|
|
||||||
showStart ? 'Начать печать' : 'Убрал листы — продолжать'
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Автостарт.
|
|
||||||
*
|
|
||||||
* Если сервер вернул задание в waiting_start, а фронт по какой-то причине
|
|
||||||
* не стартовал его сам, стартуем здесь.
|
|
||||||
*
|
|
||||||
* Если тебе нужен ТОЛЬКО ручной старт, удали этот $effect целиком.
|
|
||||||
*/
|
|
||||||
let autoStartedJobId = '';
|
let autoStartedJobId = '';
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const jobId = store.job?.job_id;
|
const jobId = store.job?.job_id;
|
||||||
|
if (!store.jobActive || !jobId) return;
|
||||||
if (!store.jobActive) return;
|
|
||||||
if (!jobId) return;
|
|
||||||
if (jobPhase !== 'waiting_start') return;
|
|
||||||
if (autoStartedJobId === jobId) return;
|
if (autoStartedJobId === jobId) return;
|
||||||
|
if (!store.autoContinue) return;
|
||||||
|
|
||||||
|
// НЕ стартуем автоматически, если нужно убрать листы (требуется физическое действие)
|
||||||
|
if (showClearOutput) return;
|
||||||
|
|
||||||
autoStartedJobId = jobId;
|
// Автостарт для WaitingStart (начало задания)
|
||||||
|
if (jobPhase === 'waiting_start') {
|
||||||
setTimeout(() => {
|
autoStartedJobId = jobId;
|
||||||
if (
|
setTimeout(() => {
|
||||||
store.job?.job_id === jobId &&
|
if (store.job?.job_id === jobId && norm(store.job?.phase) === 'waiting_start') {
|
||||||
store.jobActive &&
|
store.advanceJob();
|
||||||
norm(store.job?.phase) === 'waiting_start'
|
}
|
||||||
) {
|
}, 50);
|
||||||
store.advanceJob();
|
}
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
// Целевые файлы для подсказок
|
||||||
* В какой карточке файла показывать инструкцию.
|
|
||||||
*/
|
|
||||||
const flipTarget = $derived(
|
const flipTarget = $derived(
|
||||||
showFlip
|
showFlip ? (jobFiles.find((f) => norm(f.status) === 'awaiting_flip') ?? null) : null
|
||||||
? (jobFiles.find((f) => norm(f.status) === 'awaiting_flip') ?? null)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const pickupTarget = $derived(
|
const pickupTarget = $derived(
|
||||||
showPickup
|
showPickup ? (jobFiles.find((f) => norm(f.status) === 'awaiting_pickup') ?? null) : null
|
||||||
? (jobFiles.find((f) => norm(f.status) === 'awaiting_pickup') ?? null)
|
);
|
||||||
: null
|
const clearTarget = $derived(
|
||||||
|
showClearOutput ? (jobFiles.find((f) => norm(f.status) === 'awaiting_clear_output') ?? null) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearTarget = $derived.by(() => {
|
|
||||||
if (!showClearOutput) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
jobFiles.find((f) => norm(f.status) === 'awaiting_clear_output') ??
|
|
||||||
(jobPhase !== 'waiting_start'
|
|
||||||
? jobFiles.find((f) => norm(f.status) === 'printing')
|
|
||||||
: undefined) ??
|
|
||||||
jobFiles.find((f) => norm(f.status) === 'queued') ??
|
|
||||||
null
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @returns {'clear' | 'flip' | 'pickup' | null} */
|
/** @returns {'clear' | 'flip' | 'pickup' | null} */
|
||||||
function hintFor(f) {
|
function hintFor(f) {
|
||||||
@ -176,6 +116,7 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- УДЕРЖАНИЕ ДЛЯ ОТМЕНЫ ---
|
||||||
const HOLD_MS = 1500;
|
const HOLD_MS = 1500;
|
||||||
let hold = $state(0);
|
let hold = $state(0);
|
||||||
let raf = 0;
|
let raf = 0;
|
||||||
@ -183,38 +124,19 @@
|
|||||||
|
|
||||||
function startHold(e) {
|
function startHold(e) {
|
||||||
if (!store.jobActive || !canCancel) return;
|
if (!store.jobActive || !canCancel) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.currentTarget.setPointerCapture?.(e.pointerId);
|
e.currentTarget.setPointerCapture?.(e.pointerId);
|
||||||
|
|
||||||
t0 = performance.now();
|
t0 = performance.now();
|
||||||
|
|
||||||
const step = (now) => {
|
const step = (now) => {
|
||||||
hold = Math.min(1, (now - t0) / HOLD_MS);
|
hold = Math.min(1, (now - t0) / HOLD_MS);
|
||||||
|
if (hold >= 1) { raf = 0; hold = 0; store.cancelJob(); return; }
|
||||||
if (hold >= 1) {
|
|
||||||
raf = 0;
|
|
||||||
hold = 0;
|
|
||||||
store.cancelJob();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
raf = requestAnimationFrame(step);
|
raf = requestAnimationFrame(step);
|
||||||
};
|
};
|
||||||
|
|
||||||
raf = requestAnimationFrame(step);
|
raf = requestAnimationFrame(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
function endHold() {
|
function endHold() { if (raf) cancelAnimationFrame(raf); raf = 0; hold = 0; }
|
||||||
if (raf) cancelAnimationFrame(raf);
|
function finish() { store.resetJob(); onBack(); }
|
||||||
raf = 0;
|
|
||||||
hold = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function finish() {
|
|
||||||
store.resetJob();
|
|
||||||
onBack();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
@ -236,9 +158,7 @@
|
|||||||
<section class="file-card" class:current={isActive}>
|
<section class="file-card" class:current={isActive}>
|
||||||
<div class="file-row">
|
<div class="file-row">
|
||||||
{#if b}<span class="type-badge">{b}</span>{/if}
|
{#if b}<span class="type-badge">{b}</span>{/if}
|
||||||
|
|
||||||
<span class="file-name" title={f.name}>{f.name}</span>
|
<span class="file-name" title={f.name}>{f.name}</span>
|
||||||
|
|
||||||
<span class="file-status st-{st}">
|
<span class="file-status st-{st}">
|
||||||
{STATUS_LABEL[st] ?? f.status}
|
{STATUS_LABEL[st] ?? f.status}
|
||||||
</span>
|
</span>
|
||||||
@ -246,23 +166,12 @@
|
|||||||
|
|
||||||
{#if hint === 'clear'}
|
{#if hint === 'clear'}
|
||||||
<ol class="pickup-hint clear-hint">
|
<ol class="pickup-hint clear-hint">
|
||||||
{#if !showStart}
|
<li>Уберите распечатанные листы из выходного лотка</li>
|
||||||
<li>Уберите распечатанные листы из выходного лотка</li>
|
<li>Нажмите кнопку ниже для начала печати этого файла</li>
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if clearActionAvailable}
|
|
||||||
<li>
|
|
||||||
Нажмите кнопку ниже
|
|
||||||
{showStart ? 'для начала печати' : 'для продолжения'}
|
|
||||||
</li>
|
|
||||||
{:else}
|
|
||||||
<li>Дождитесь завершения первого прохода</li>
|
|
||||||
{/if}
|
|
||||||
</ol>
|
</ol>
|
||||||
{:else if hint === 'flip'}
|
{:else if hint === 'flip'}
|
||||||
<ol class="pickup-hint flip-hint">
|
<ol class="pickup-hint flip-hint">
|
||||||
<li>Не переворачивая, положите листы в слот ручной подачи</li>
|
<li>Не переворачивая, положите листы в слот ручной подачи</li>
|
||||||
|
|
||||||
{#if flipActionAvailable}
|
{#if flipActionAvailable}
|
||||||
<li>Нажмите кнопку ниже для печати обратной стороны</li>
|
<li>Нажмите кнопку ниже для печати обратной стороны</li>
|
||||||
{:else}
|
{:else}
|
||||||
@ -272,7 +181,6 @@
|
|||||||
{:else if hint === 'pickup'}
|
{:else if hint === 'pickup'}
|
||||||
<ol class="pickup-hint">
|
<ol class="pickup-hint">
|
||||||
<li>Дождитесь завершения печати</li>
|
<li>Дождитесь завершения печати</li>
|
||||||
|
|
||||||
{#if pickupActionAvailable}
|
{#if pickupActionAvailable}
|
||||||
<li>Нажмите кнопку ниже для подтверждения</li>
|
<li>Нажмите кнопку ниже для подтверждения</li>
|
||||||
{:else}
|
{:else}
|
||||||
@ -304,10 +212,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if store.jobActive}
|
{#if store.jobActive}
|
||||||
{#if showStart}
|
{#if clearActionAvailable}
|
||||||
<button class="next-btn" onclick={() => store.advanceJob()}>
|
<button class="next-btn next-btn-clear" onclick={() => store.advanceJob()}>
|
||||||
{clearButtonLabel}
|
{clearButtonLabel}
|
||||||
</button>
|
</button>
|
||||||
|
{:else if showStart}
|
||||||
|
<!-- Показываем только если автостарт выключен, иначе уедет само -->
|
||||||
|
<button class="next-btn" onclick={() => store.advanceJob()}>
|
||||||
|
Начать печать
|
||||||
|
</button>
|
||||||
{:else if pickupActionAvailable}
|
{:else if pickupActionAvailable}
|
||||||
<button class="next-btn" onclick={() => store.advanceJob()}>
|
<button class="next-btn" onclick={() => store.advanceJob()}>
|
||||||
Печать закончена — далее
|
Печать закончена — далее
|
||||||
@ -316,10 +229,6 @@
|
|||||||
<button class="next-btn next-btn-flip" onclick={() => store.advanceJob()}>
|
<button class="next-btn next-btn-flip" onclick={() => store.advanceJob()}>
|
||||||
Переложил — печатать обратную сторону
|
Переложил — печатать обратную сторону
|
||||||
</button>
|
</button>
|
||||||
{:else if clearActionAvailable}
|
|
||||||
<button class="next-btn next-btn-clear" onclick={() => store.advanceJob()}>
|
|
||||||
{clearButtonLabel}
|
|
||||||
</button>
|
|
||||||
{:else}
|
{:else}
|
||||||
<button class="next-btn next-btn-disabled" disabled>
|
<button class="next-btn next-btn-disabled" disabled>
|
||||||
{currentStatus === 'printing' ? 'Отправка...' : 'Ожидание...'}
|
{currentStatus === 'printing' ? 'Отправка...' : 'Ожидание...'}
|
||||||
@ -399,10 +308,7 @@
|
|||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn:disabled {
|
.back-btn:disabled { opacity: 0.3; cursor: not-allowed; }
|
||||||
opacity: 0.3;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
.header h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -459,11 +365,7 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-status {
|
.file-status { flex-shrink: 0; font-size: 14px; font-weight: 700; }
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-badge {
|
.type-badge {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@ -489,9 +391,7 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pickup-hint li {
|
.pickup-hint li { text-align: left; }
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-error {
|
.file-error {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -520,12 +420,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auto-row input {
|
.auto-row input { width: 18px; height: 18px; accent-color: #6366f1; cursor: pointer; }
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
accent-color: #6366f1;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.next-btn {
|
.next-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -577,25 +472,17 @@
|
|||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-btn:disabled {
|
.cancel-btn:disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
|
||||||
opacity: 0.4;
|
|
||||||
cursor: not-allowed;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-fill {
|
.cancel-fill {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0; top: 0; bottom: 0;
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 0%;
|
width: 0%;
|
||||||
background: rgba(239,68,68,0.35);
|
background: rgba(239,68,68,0.35);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-label {
|
.cancel-label { position: relative; }
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hold-hint {
|
.hold-hint {
|
||||||
margin: -4px 0 0;
|
margin: -4px 0 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user