@cardinali-studio/adminkit 2.2.25 → 2.2.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +19 -0
- package/dist/api.js +3 -0
- package/dist/app/App.js +22 -8
- package/dist/app/app.css +3 -3
- package/dist/app/delivery.d.ts +11 -0
- package/dist/app/delivery.js +46 -0
- package/dist/app/index.d.ts +4 -1
- package/dist/app/index.js +3 -2
- package/dist/app/lib/bookings.js +5 -0
- package/dist/app/lib/deliveries.d.ts +8 -0
- package/dist/app/lib/deliveries.js +7 -0
- package/dist/app/lib/delivery-network.d.ts +25 -0
- package/dist/app/lib/delivery-network.js +159 -0
- package/dist/app/lib/delivery-upload.d.ts +29 -11
- package/dist/app/lib/delivery-upload.js +408 -131
- package/dist/app/lib/home.js +7 -4
- package/dist/app/lib/pages.js +2 -1
- package/dist/app/lib/publish.d.ts +5 -0
- package/dist/app/lib/publish.js +61 -4
- package/dist/app/lib/requests.js +6 -0
- package/dist/app/lib/settings.js +7 -3
- package/dist/app/lib/upload-queue.d.ts +21 -3
- package/dist/app/lib/upload-queue.js +76 -19
- package/dist/app/routes/Deliveries.js +108 -29
- package/dist/app/routes/PageActions.d.ts +1 -1
- package/dist/app/routes/PageActions.js +5 -2
- package/dist/app/routes/PageEditor.js +6 -4
- package/dist/app/routes/PageSeoEditor.js +6 -1
- package/dist/app/routes/PagesList.js +5 -1
- package/dist/app/routes/UploadDock.js +6 -2
- package/dist/app/site.d.ts +13 -0
- package/dist/direct-publication.d.ts +20 -0
- package/dist/direct-publication.js +75 -0
- package/dist/functions/_shared/b2.js +22 -3
- package/dist/functions/_shared/delivery-crypto.js +5 -0
- package/dist/functions/_shared/delivery-limits.d.ts +9 -0
- package/dist/functions/_shared/delivery-limits.js +29 -0
- package/dist/functions/_shared/delivery-manifest-v2.sql +49 -0
- package/dist/functions/_shared/delivery-manifest.js +392 -0
- package/dist/functions/_shared/delivery-purge.js +17 -1
- package/dist/functions/api/deliveries.js +177 -49
- package/dist/functions/api/delivery/image.js +11 -6
- package/dist/functions/api/delivery/multipart.js +196 -64
- package/dist/functions/delivery-public/assets/client.js +246 -0
- package/dist/functions/delivery-public/assets/delivery.css +297 -0
- package/dist/functions/delivery-public/assets/gallery.js +243 -0
- package/dist/functions/delivery-public/assets/stage.js +107 -0
- package/dist/functions/delivery-public/index.js +637 -0
- package/dist/functions/delivery-public/migrations/0002_deliveries.sql +128 -0
- package/dist/functions/delivery-public/migrations/0003_delivery_quick_hero.sql +34 -0
- package/dist/functions/delivery-public/migrations/0004_delivery_file_expiry.sql +9 -0
- package/dist/functions/delivery-public/migrations/0006_delivery_manifest_v2.sql +49 -0
- package/dist/functions/delivery-public/migrations/0007_delivery_link_aliases.sql +8 -0
- package/dist/functions/delivery-public/migrations/0008_delivery_web_format.sql +2 -0
- package/dist/functions/delivery-public/migrations/0009_delivery_download_labels.sql +2 -0
- package/dist/functions/index.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +10 -3
package/dist/app/lib/home.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* l'admin per riordinare le pagine non ha motivo di pagare una chiamata in più — e senza
|
|
9
9
|
* GITHUB_TOKEN, in anteprima locale, quella chiamata è un 500.
|
|
10
10
|
*/
|
|
11
|
-
import { useCallback, useEffect, useState } from "react";
|
|
11
|
+
import { useCallback, useEffect, useState, useRef } from "react";
|
|
12
12
|
import { ApiError } from "../../index.js";
|
|
13
13
|
import { api } from "../../api.js";
|
|
14
14
|
import { contentPersistenceMode, site } from "../site.js";
|
|
@@ -27,6 +27,7 @@ export function useHome(active) {
|
|
|
27
27
|
const [saving, setSaving] = useState(false);
|
|
28
28
|
const [notice, setNotice] = useState(null);
|
|
29
29
|
const [revision, setRevision] = useState(null);
|
|
30
|
+
const pending = useRef(null);
|
|
30
31
|
const load = useCallback(async () => {
|
|
31
32
|
try {
|
|
32
33
|
const mapped = site().homeDocument;
|
|
@@ -59,12 +60,14 @@ export function useHome(active) {
|
|
|
59
60
|
try {
|
|
60
61
|
const mapped = site().homeDocument;
|
|
61
62
|
if (contentPersistenceMode() === "d1" && mapped && revision) {
|
|
62
|
-
const revisionId = crypto.randomUUID();
|
|
63
|
+
const revisionId = pending.current?.revisionId ?? crypto.randomUUID();
|
|
63
64
|
/* The Worker validates and may normalize a document before checking its signed
|
|
64
65
|
* representation. Sign the site-owned canonical payload here too, otherwise an
|
|
65
66
|
* obsolete/unknown client field becomes a misleading "documento non valido". */
|
|
66
67
|
const payload = mapped.toPayload ? mapped.toPayload(data) : data;
|
|
67
|
-
|
|
68
|
+
pending.current ||= { revisionId, documentId: mapped.documentId, baseRevisionId: revision.base, expectedDraftRevisionId: revision.expected, kind: mapped.kind, locale: mapped.locale, schemaVersion: mapped.schemaVersion, payload: payload, tombstone: false };
|
|
69
|
+
await api.saveContent(pending.current);
|
|
70
|
+
pending.current = null;
|
|
68
71
|
setRevision({ base: revisionId, expected: revisionId });
|
|
69
72
|
setNotice("Bozza D1 salvata");
|
|
70
73
|
setDirty(false);
|
|
@@ -88,7 +91,7 @@ export function useHome(active) {
|
|
|
88
91
|
setSaving(false);
|
|
89
92
|
}
|
|
90
93
|
}, [data, revision]);
|
|
91
|
-
const change = useCallback((next) => { setData(next); setDirty(true); }, []);
|
|
94
|
+
const change = useCallback((next) => { pending.current = null; setData(next); setDirty(true); }, []);
|
|
92
95
|
const mapped = site().homeDocument;
|
|
93
96
|
return { data, error, dirty, saving, notice, load, change, save, setNotice,
|
|
94
97
|
publishTarget: contentPersistenceMode() === "d1" && mapped && revision
|
package/dist/app/lib/pages.js
CHANGED
|
@@ -48,7 +48,8 @@ export function usePages() {
|
|
|
48
48
|
}, []);
|
|
49
49
|
useEffect(() => { void load(); }, [load]);
|
|
50
50
|
const update = useCallback((next) => {
|
|
51
|
-
|
|
51
|
+
if (site().pagesReorderable !== false)
|
|
52
|
+
next.forEach((p, i) => { p.weight = site().positionWeight(p, i, next); });
|
|
52
53
|
setPages(next);
|
|
53
54
|
setDirty(true);
|
|
54
55
|
}, []);
|
|
@@ -17,6 +17,11 @@ export interface PublishState {
|
|
|
17
17
|
reload(): Promise<void>;
|
|
18
18
|
publish(target?: ContentPublishTarget): Promise<void>;
|
|
19
19
|
clearNotice(): void;
|
|
20
|
+
preview(target: ContentPublishTarget): Promise<void>;
|
|
21
|
+
previewApproved(target: ContentPublishTarget | null): boolean;
|
|
22
|
+
previousReleaseId: string | null;
|
|
23
|
+
rollback(): Promise<void>;
|
|
24
|
+
verify(): Promise<void>;
|
|
20
25
|
}
|
|
21
26
|
export declare function contentPublicationNotice(result: {
|
|
22
27
|
published: boolean;
|
package/dist/app/lib/publish.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { useCallback, useEffect, useState } from "react";
|
|
9
9
|
import { api, ApiError } from "../../index.js";
|
|
10
|
-
import { contentPersistenceMode } from "../site.js";
|
|
10
|
+
import { contentPersistenceMode, site } from "../site.js";
|
|
11
11
|
export function contentPublicationNotice(result) {
|
|
12
12
|
if (result.published === false) {
|
|
13
13
|
return result.message ?? "Non c'erano modifiche salvate da pubblicare.";
|
|
@@ -25,10 +25,64 @@ export function usePublish() {
|
|
|
25
25
|
const [publishing, setPublishing] = useState(false);
|
|
26
26
|
const [notice, setNotice] = useState(null);
|
|
27
27
|
const [error, setError] = useState(null);
|
|
28
|
+
const [approval, setApproval] = useState(null);
|
|
29
|
+
const [activeReleaseId, setActiveReleaseId] = useState(null);
|
|
30
|
+
const [previousReleaseId, setPreviousReleaseId] = useState(null);
|
|
31
|
+
const previewApproved = (target) => !!target && "documentId" in target && !!approval && approval.documentId === target.documentId && approval.expectedDraftRevisionId === target.expectedDraftRevisionId && approval.expectedBaseReleaseId === activeReleaseId;
|
|
32
|
+
const preview = async (target) => {
|
|
33
|
+
if (!("documentId" in target))
|
|
34
|
+
return;
|
|
35
|
+
setPublishing(true);
|
|
36
|
+
setNotice(null);
|
|
37
|
+
try {
|
|
38
|
+
const result = await api.contentPreview(target);
|
|
39
|
+
setApproval({ ...result, documentId: target.documentId });
|
|
40
|
+
window.open(result.previewUrl, "_blank", "noopener");
|
|
41
|
+
setNotice("Anteprima della bozza salvata pronta.");
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
setApproval(null);
|
|
45
|
+
setNotice(error.message);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
setPublishing(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const rollback = async () => {
|
|
52
|
+
if (!activeReleaseId || !previousReleaseId)
|
|
53
|
+
return;
|
|
54
|
+
setPublishing(true);
|
|
55
|
+
setNotice(null);
|
|
56
|
+
try {
|
|
57
|
+
await api.contentRollback(activeReleaseId, previousReleaseId);
|
|
58
|
+
setApproval(null);
|
|
59
|
+
await reload();
|
|
60
|
+
setNotice("Ripristinata la release precedente. Le bozze sono conservate.");
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
setNotice(error.message);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
setPublishing(false);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const verify = async () => {
|
|
70
|
+
if (!activeReleaseId)
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
const result = await api.contentVerification(activeReleaseId);
|
|
74
|
+
setNotice(result.publicOriginVerified ? "Release verificata sul dominio pubblico." : result.reason);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
setNotice(error.message);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
28
80
|
const reload = useCallback(async () => {
|
|
29
81
|
if (contentPersistenceMode() === "d1") {
|
|
30
82
|
try {
|
|
31
83
|
const d = await api.contentPublishState();
|
|
84
|
+
setActiveReleaseId(d.activeReleaseId ?? null);
|
|
85
|
+
setPreviousReleaseId(d.previousReleaseId ?? null);
|
|
32
86
|
setAhead(d.ahead ?? 0);
|
|
33
87
|
setDocuments(d.documents ?? []);
|
|
34
88
|
setSiteAccessPending(Boolean(d.siteAccess));
|
|
@@ -78,7 +132,10 @@ export function usePublish() {
|
|
|
78
132
|
try {
|
|
79
133
|
if (!target)
|
|
80
134
|
throw new ApiError("Apri il contenuto che vuoi pubblicare.", "invalid_request", 400);
|
|
81
|
-
|
|
135
|
+
if (site().directPublication && !previewApproved(target))
|
|
136
|
+
throw new ApiError("Apri una nuova anteprima della bozza prima di pubblicare.", "stale_preview", 409);
|
|
137
|
+
const r = await api.contentPublish(site().directPublication ? { ...target, ...approval } : target);
|
|
138
|
+
setApproval(null);
|
|
82
139
|
setNotice(contentPublicationNotice(r));
|
|
83
140
|
await reload();
|
|
84
141
|
}
|
|
@@ -113,9 +170,9 @@ export function usePublish() {
|
|
|
113
170
|
finally {
|
|
114
171
|
setPublishing(false);
|
|
115
172
|
}
|
|
116
|
-
}, [reload]);
|
|
173
|
+
}, [reload, approval, activeReleaseId]);
|
|
117
174
|
return {
|
|
118
175
|
ahead, commits, documents, siteAccessPending, configured, publishing, notice, error,
|
|
119
|
-
reload, publish, clearNotice: () => setNotice(null),
|
|
176
|
+
reload, publish, preview, previewApproved, previousReleaseId, rollback, verify, clearNotice: () => setNotice(null),
|
|
120
177
|
};
|
|
121
178
|
}
|
package/dist/app/lib/requests.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { site } from "../site.js";
|
|
1
2
|
/* Le richieste dal form di contatto.
|
|
2
3
|
*
|
|
3
4
|
* L'archivio è la SORGENTE, non l'email: `functions/api/contact.js` salva PRIMA e manda dopo, e
|
|
@@ -15,6 +16,11 @@ export function useRequests() {
|
|
|
15
16
|
const [error, setError] = useState(null);
|
|
16
17
|
const [busy, setBusy] = useState(false);
|
|
17
18
|
const load = useCallback(async () => {
|
|
19
|
+
if (site().modules?.requests !== true) {
|
|
20
|
+
setRows([]);
|
|
21
|
+
setError(null);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
18
24
|
try {
|
|
19
25
|
setRows((await api.requests()).requests);
|
|
20
26
|
setError(null);
|
package/dist/app/lib/settings.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* protegge il sito. La password digitata resta soltanto nello stato del form: il backend
|
|
5
5
|
* restituisce sempre il campo vuoto e conserva esclusivamente la verifica crittografica.
|
|
6
6
|
*/
|
|
7
|
-
import { useCallback, useEffect, useState } from "react";
|
|
7
|
+
import { useCallback, useEffect, useState, useRef } from "react";
|
|
8
8
|
import { ApiError } from "../../index.js";
|
|
9
9
|
import { api } from "../../api.js";
|
|
10
10
|
import { contentPersistenceMode, site } from "../site.js";
|
|
@@ -43,6 +43,7 @@ export function useSiteSettings(active) {
|
|
|
43
43
|
const { password: _password, passwordEnabled: _passwordEnabled, ...editorial } = settings;
|
|
44
44
|
return JSON.stringify(editorial);
|
|
45
45
|
};
|
|
46
|
+
const pending = useRef(null);
|
|
46
47
|
const load = useCallback(async () => {
|
|
47
48
|
try {
|
|
48
49
|
const mapped = site().siteSettingsDocument;
|
|
@@ -88,9 +89,11 @@ export function useSiteSettings(active) {
|
|
|
88
89
|
const mapped = site().siteSettingsDocument;
|
|
89
90
|
if (contentPersistenceMode() === "d1" && mapped && revision) {
|
|
90
91
|
if (editorialSnapshot(data) !== editorialBaseline) {
|
|
91
|
-
const revisionId = crypto.randomUUID();
|
|
92
|
+
const revisionId = pending.current?.revisionId ?? crypto.randomUUID();
|
|
92
93
|
const payload = mapped.toPayload ? mapped.toPayload(data) : data;
|
|
93
|
-
|
|
94
|
+
pending.current ||= { revisionId, documentId: mapped.documentId, baseRevisionId: revision.base, expectedDraftRevisionId: revision.expected, kind: mapped.kind, locale: mapped.locale, schemaVersion: mapped.schemaVersion, payload: payload, tombstone: false };
|
|
95
|
+
await api.saveContent(pending.current);
|
|
96
|
+
pending.current = null;
|
|
94
97
|
setRevision({ base: revisionId, expected: revisionId });
|
|
95
98
|
setEditorialBaseline(editorialSnapshot(data));
|
|
96
99
|
}
|
|
@@ -124,6 +127,7 @@ export function useSiteSettings(active) {
|
|
|
124
127
|
}
|
|
125
128
|
}, [data, revision, accessState, editorialBaseline]);
|
|
126
129
|
const change = useCallback((next) => {
|
|
130
|
+
pending.current = null;
|
|
127
131
|
setData(next);
|
|
128
132
|
setDirty(true);
|
|
129
133
|
}, []);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type DeliveryUploadProgress } from "./delivery-upload";
|
|
2
|
-
export type UploadState = "running" | "paused" | "done" | "failed";
|
|
1
|
+
import { type DeliveryUploadProgress, type UploadControl } from "./delivery-upload.js";
|
|
2
|
+
export type UploadState = "running" | "paused" | "cancelling" | "done" | "failed";
|
|
3
3
|
export interface UploadJob {
|
|
4
4
|
/** L'id del pacchetto: uno per caricamento. */
|
|
5
5
|
id: string;
|
|
@@ -11,6 +11,8 @@ export interface UploadJob {
|
|
|
11
11
|
progress: DeliveryUploadProgress | null;
|
|
12
12
|
error?: string;
|
|
13
13
|
skipped?: number;
|
|
14
|
+
waitingForNetwork?: boolean;
|
|
15
|
+
retryable?: boolean;
|
|
14
16
|
}
|
|
15
17
|
interface StartInput {
|
|
16
18
|
deliveryId: string;
|
|
@@ -20,16 +22,32 @@ interface StartInput {
|
|
|
20
22
|
options: {
|
|
21
23
|
galleryEnabled: boolean;
|
|
22
24
|
webEnabled: boolean;
|
|
25
|
+
webFormat?: "webp" | "jpg";
|
|
26
|
+
manifestVersion?: 1 | 2;
|
|
23
27
|
};
|
|
24
28
|
}
|
|
25
29
|
export declare function useUploads(): UploadJob[];
|
|
26
30
|
export declare function useUploadCompletions(): number;
|
|
27
31
|
export declare function uploadFor(deliveryId: string, list: UploadJob[]): UploadJob | undefined;
|
|
28
32
|
export declare function isBusy(job: UploadJob | undefined): boolean;
|
|
29
|
-
export declare
|
|
33
|
+
export declare class Gate implements UploadControl {
|
|
34
|
+
private id;
|
|
35
|
+
readonly controller: AbortController;
|
|
36
|
+
readonly signal: AbortSignal;
|
|
37
|
+
private waitingRequests;
|
|
38
|
+
constructor(id: string);
|
|
39
|
+
networkWaiting(waiting: boolean): void;
|
|
40
|
+
paused: boolean;
|
|
41
|
+
cancelled: boolean;
|
|
42
|
+
private waiting;
|
|
43
|
+
gate(): Promise<void>;
|
|
44
|
+
wake(): void;
|
|
45
|
+
}
|
|
46
|
+
export declare function startUpload(input: StartInput): Promise<void>;
|
|
30
47
|
export declare function pauseUpload(id: string): void;
|
|
31
48
|
export declare function resumeUpload(id: string): void;
|
|
32
49
|
export declare function cancelUpload(id: string): void;
|
|
50
|
+
export declare function retryUpload(id: string): Promise<void> | undefined;
|
|
33
51
|
export declare function dismissUpload(id: string): void;
|
|
34
52
|
export declare function hasRunningUploads(): boolean;
|
|
35
53
|
export {};
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
* App.tsx ricarica l'elenco quando uno finisce (`useUploadCompletions`)
|
|
16
16
|
*/
|
|
17
17
|
import { useSyncExternalStore } from "react";
|
|
18
|
-
import { UploadCancelled, uploadDeliveryPackage, } from "./delivery-upload";
|
|
19
|
-
import {
|
|
18
|
+
import { UploadCancelled, uploadDeliveryPackage, } from "./delivery-upload.js";
|
|
19
|
+
import { deliveryJson } from "./delivery-network.js";
|
|
20
20
|
/* ---- lo stato ------------------------------------------------------------------------ */
|
|
21
21
|
let jobs = [];
|
|
22
22
|
let completions = 0;
|
|
23
23
|
const gates = new Map();
|
|
24
|
+
const running = new Map();
|
|
25
|
+
const retries = new Map();
|
|
24
26
|
const listeners = new Set();
|
|
25
27
|
function emit() {
|
|
26
28
|
jobs = [...jobs];
|
|
@@ -47,15 +49,27 @@ export function useUploadCompletions() {
|
|
|
47
49
|
return useSyncExternalStore(subscribe, () => completions, () => completions);
|
|
48
50
|
}
|
|
49
51
|
export function uploadFor(deliveryId, list) {
|
|
50
|
-
|
|
52
|
+
const matching = list.filter((job) => job.deliveryId === deliveryId);
|
|
53
|
+
return matching.find(isBusy) || matching[matching.length - 1];
|
|
51
54
|
}
|
|
52
55
|
export function isBusy(job) {
|
|
53
|
-
return !!job && (job.state === "running" || job.state === "paused");
|
|
56
|
+
return !!job && (job.state === "running" || job.state === "paused" || job.state === "cancelling");
|
|
54
57
|
}
|
|
55
58
|
/* ---- il cancello --------------------------------------------------------------------- */
|
|
56
59
|
/* Pausa e annullamento passano da qui: il motore (delivery-upload.ts) chiama `gate()` a ogni
|
|
57
60
|
file e a ogni parte, e questa promessa lo tiene fermo finché non si riprende. */
|
|
58
|
-
class Gate {
|
|
61
|
+
export class Gate {
|
|
62
|
+
id;
|
|
63
|
+
controller = new AbortController();
|
|
64
|
+
signal = this.controller.signal;
|
|
65
|
+
waitingRequests = 0;
|
|
66
|
+
constructor(id) {
|
|
67
|
+
this.id = id;
|
|
68
|
+
}
|
|
69
|
+
networkWaiting(waiting) {
|
|
70
|
+
this.waitingRequests += waiting ? 1 : -1;
|
|
71
|
+
patch(this.id, { waitingForNetwork: this.waitingRequests > 0 });
|
|
72
|
+
}
|
|
59
73
|
paused = false;
|
|
60
74
|
cancelled = false;
|
|
61
75
|
waiting = [];
|
|
@@ -77,7 +91,10 @@ class Gate {
|
|
|
77
91
|
}
|
|
78
92
|
/* ---- i comandi ------------------------------------------------------------------------ */
|
|
79
93
|
export function startUpload(input) {
|
|
80
|
-
const
|
|
94
|
+
const existing = running.get(input.packageId);
|
|
95
|
+
if (existing)
|
|
96
|
+
return existing;
|
|
97
|
+
const gate = new Gate(input.packageId);
|
|
81
98
|
gates.set(input.packageId, gate);
|
|
82
99
|
jobs = [...jobs.filter((job) => job.id !== input.packageId), {
|
|
83
100
|
id: input.packageId,
|
|
@@ -89,33 +106,64 @@ export function startUpload(input) {
|
|
|
89
106
|
progress: null,
|
|
90
107
|
}];
|
|
91
108
|
emit();
|
|
92
|
-
|
|
109
|
+
const promise = run(input, gate).finally(() => running.delete(input.packageId));
|
|
110
|
+
running.set(input.packageId, promise);
|
|
111
|
+
return promise;
|
|
93
112
|
}
|
|
94
113
|
async function run(input, gate) {
|
|
95
114
|
const id = input.packageId;
|
|
96
115
|
try {
|
|
97
|
-
const
|
|
116
|
+
const retry = retries.get(id);
|
|
117
|
+
if (retry?.cleanup)
|
|
118
|
+
throw new UploadCancelled();
|
|
119
|
+
const result = retry?.result || await uploadDeliveryPackage(input.deliveryId, id, input.files, input.options, (progress) => patch(id, { progress }), gate);
|
|
98
120
|
/* Chiusura e attivazione stanno QUI e non nella schermata: se finiscono mentre il
|
|
99
121
|
fotografo sta guardando un'altra sezione, devono succedere lo stesso. */
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
retries.set(id, { input, result });
|
|
123
|
+
await gate.gate();
|
|
124
|
+
if (result.manifest.version === 2) {
|
|
125
|
+
let done = false;
|
|
126
|
+
while (!done) {
|
|
127
|
+
await gate.gate();
|
|
128
|
+
const step = await deliveryJson("/admin/api/deliveries", {
|
|
129
|
+
action: "manifestFinalize", id: input.deliveryId, packageId: id,
|
|
130
|
+
}, gate);
|
|
131
|
+
done = step.done;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else
|
|
135
|
+
await deliveryJson("/admin/api/deliveries", {
|
|
136
|
+
action: "finalizePackage", id: input.deliveryId, packageId: id,
|
|
137
|
+
manifest: result.manifest, fileCount: result.fileCount,
|
|
138
|
+
sourceBytes: result.sourceBytes, storedBytes: result.storedBytes,
|
|
139
|
+
}, gate);
|
|
140
|
+
await gate.gate();
|
|
141
|
+
await deliveryJson("/admin/api/deliveries", { action: "activate", id: input.deliveryId, packageId: id }, gate);
|
|
142
|
+
await gate.gate();
|
|
143
|
+
retries.delete(id);
|
|
106
144
|
patch(id, { state: "done", progress: null, skipped: result.manifest.skipped.length });
|
|
107
145
|
}
|
|
108
146
|
catch (error) {
|
|
109
147
|
if (error instanceof UploadCancelled) {
|
|
110
148
|
/* Annullare vuol dire «come se non l'avessi mai iniziato»: il pacchetto sparisce, e
|
|
111
149
|
con lui il multipart aperto su B2. */
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
150
|
+
patch(id, { state: "cancelling", progress: null });
|
|
151
|
+
try {
|
|
152
|
+
await deliveryJson("/admin/api/deliveries", {
|
|
153
|
+
action: "deletePackage", id: input.deliveryId, packageId: id,
|
|
154
|
+
}, { gate: async () => { }, networkWaiting: (waiting) => gate.networkWaiting(waiting) });
|
|
155
|
+
retries.delete(id);
|
|
156
|
+
jobs = jobs.filter((job) => job.id !== id);
|
|
157
|
+
}
|
|
158
|
+
catch (cleanupError) {
|
|
159
|
+
retries.set(id, { input, cleanup: true });
|
|
160
|
+
patch(id, { state: "failed", retryable: true, error: "Upload fermato. Riprova la rimozione dei file: " + readable(cleanupError) });
|
|
161
|
+
}
|
|
116
162
|
}
|
|
117
163
|
else {
|
|
118
|
-
|
|
164
|
+
if (!retries.has(id))
|
|
165
|
+
retries.set(id, { input });
|
|
166
|
+
patch(id, { state: "failed", progress: null, retryable: true, error: readable(error) });
|
|
119
167
|
}
|
|
120
168
|
}
|
|
121
169
|
finally {
|
|
@@ -144,12 +192,21 @@ export function cancelUpload(id) {
|
|
|
144
192
|
if (!gate)
|
|
145
193
|
return;
|
|
146
194
|
gate.cancelled = true;
|
|
195
|
+
gate.controller.abort();
|
|
196
|
+
patch(id, { state: "cancelling" });
|
|
147
197
|
gate.paused = false;
|
|
148
198
|
gate.wake();
|
|
149
199
|
}
|
|
150
200
|
/* Toglie dalla vista un caricamento già finito. Su uno in corso non fa niente: per quello
|
|
151
201
|
c'è `cancelUpload`, che è un'altra cosa e va detta con un'altra parola. */
|
|
202
|
+
export function retryUpload(id) {
|
|
203
|
+
const retry = retries.get(id);
|
|
204
|
+
if (retry)
|
|
205
|
+
return startUpload(retry.input);
|
|
206
|
+
}
|
|
152
207
|
export function dismissUpload(id) {
|
|
208
|
+
if (!gates.has(id))
|
|
209
|
+
retries.delete(id);
|
|
153
210
|
jobs = jobs.filter((job) => job.id === id ? isBusy(job) : true);
|
|
154
211
|
emit();
|
|
155
212
|
}
|