analytica-frontend-lib 1.5.13 → 1.5.15
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/ActivitiesHistory/index.js +5 -5
- package/dist/ActivitiesHistory/index.mjs +4 -4
- package/dist/ActivityCardQuestionBanks/index.js +5 -5
- package/dist/ActivityCardQuestionBanks/index.mjs +4 -4
- package/dist/ActivityCardQuestionPreview/index.js +5 -5
- package/dist/ActivityCardQuestionPreview/index.mjs +4 -4
- package/dist/ActivityCreate/index.js +5 -5
- package/dist/ActivityCreate/index.mjs +4 -4
- package/dist/ActivityDetails/index.js +5 -5
- package/dist/ActivityDetails/index.mjs +4 -4
- package/dist/ActivityFilters/index.js +5 -5
- package/dist/ActivityFilters/index.mjs +4 -4
- package/dist/ActivityPageLayout/index.js +5 -5
- package/dist/ActivityPageLayout/index.mjs +4 -4
- package/dist/ActivityPreview/index.js +5 -5
- package/dist/ActivityPreview/index.mjs +4 -4
- package/dist/AlertManager/index.js +5 -5
- package/dist/AlertManager/index.mjs +4 -4
- package/dist/CheckBoxGroup/index.js +5 -5
- package/dist/CheckBoxGroup/index.mjs +4 -4
- package/dist/DownloadButton/index.d.ts +4 -3
- package/dist/DownloadButton/index.d.ts.map +1 -1
- package/dist/DownloadButton/index.js +8 -2
- package/dist/DownloadButton/index.js.map +1 -1
- package/dist/DownloadButton/index.mjs +7 -1
- package/dist/ExamDetailsLayout/ExamDetailsPage/index.js +5 -5
- package/dist/ExamDetailsLayout/ExamDetailsPage/index.mjs +4 -4
- package/dist/ExamPageLayout/index.js +5 -5
- package/dist/ExamPageLayout/index.mjs +4 -4
- package/dist/PrintableUsersTable/index.js +5 -5
- package/dist/PrintableUsersTable/index.mjs +4 -4
- package/dist/RecommendedLessonCreate/index.js +5 -5
- package/dist/RecommendedLessonCreate/index.mjs +4 -4
- package/dist/RecommendedLessonDetails/index.js +5 -4
- package/dist/RecommendedLessonDetails/index.js.map +1 -1
- package/dist/RecommendedLessonDetails/index.mjs +4 -3
- package/dist/RecommendedLessonsHistory/index.js +5 -5
- package/dist/RecommendedLessonsHistory/index.mjs +4 -4
- package/dist/SendActivityModal/SendActivityModal.js +5 -5
- package/dist/SendActivityModal/SendActivityModal.mjs +4 -4
- package/dist/SendActivityModal/index.js +5 -5
- package/dist/SendActivityModal/index.mjs +4 -4
- package/dist/SendLessonModal/index.js +5 -5
- package/dist/SendLessonModal/index.mjs +4 -4
- package/dist/SimulatedPerformance/SimulatedPerformanceView/index.js +5 -5
- package/dist/SimulatedPerformance/SimulatedPerformanceView/index.mjs +4 -4
- package/dist/SimulationsPage/index.js +5 -5
- package/dist/SimulationsPage/index.mjs +4 -4
- package/dist/TableProvider/index.js +5 -5
- package/dist/TableProvider/index.mjs +4 -4
- package/dist/UnifiedDraftModelPage/index.js +5 -5
- package/dist/UnifiedDraftModelPage/index.mjs +4 -4
- package/dist/UnifiedHistoryPage/index.js +5 -5
- package/dist/UnifiedHistoryPage/index.mjs +4 -4
- package/dist/VideoPlayer/index.js +8 -3
- package/dist/VideoPlayer/index.js.map +1 -1
- package/dist/VideoPlayer/index.mjs +7 -2
- package/dist/chunk-5L6CNJDJ.mjs +223 -0
- package/dist/chunk-5L6CNJDJ.mjs.map +1 -0
- package/dist/{chunk-BYJ7VD2I.mjs → chunk-5SDXK6OI.mjs} +2 -2
- package/dist/chunk-GWEXMU4A.js +223 -0
- package/dist/chunk-GWEXMU4A.js.map +1 -0
- package/dist/{chunk-AEO4XVTH.mjs → chunk-INSEDDUA.mjs} +2 -2
- package/dist/{chunk-5G4M5GTZ.js → chunk-LJEGZ7RW.js} +3 -3
- package/dist/{chunk-5G4M5GTZ.js.map → chunk-LJEGZ7RW.js.map} +1 -1
- package/dist/{chunk-CEY7BXBL.js → chunk-O3XVL3WE.js} +3 -3
- package/dist/{chunk-CEY7BXBL.js.map → chunk-O3XVL3WE.js.map} +1 -1
- package/dist/{chunk-QZU6HKXS.js → chunk-TVQJ4WOR.js} +3 -3
- package/dist/{chunk-QZU6HKXS.js.map → chunk-TVQJ4WOR.js.map} +1 -1
- package/dist/{chunk-6WH6IFNO.mjs → chunk-ZXQ6CGGV.mjs} +2 -2
- package/dist/hooks/useAppContent/index.js +5 -5
- package/dist/hooks/useAppContent/index.mjs +4 -4
- package/dist/index.js +5 -5
- package/dist/index.mjs +4 -4
- package/package.json +2 -1
- package/dist/chunk-2M2RUGQD.mjs +0 -194
- package/dist/chunk-2M2RUGQD.mjs.map +0 -1
- package/dist/chunk-QIF47QNY.js +0 -194
- package/dist/chunk-QIF47QNY.js.map +0 -1
- /package/dist/{chunk-BYJ7VD2I.mjs.map → chunk-5SDXK6OI.mjs.map} +0 -0
- /package/dist/{chunk-AEO4XVTH.mjs.map → chunk-INSEDDUA.mjs.map} +0 -0
- /package/dist/{chunk-6WH6IFNO.mjs.map → chunk-ZXQ6CGGV.mjs.map} +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProgressModal_default
|
|
3
|
+
} from "./chunk-LPCUGTMC.mjs";
|
|
4
|
+
import {
|
|
5
|
+
IconButton_default
|
|
6
|
+
} from "./chunk-YP3VXXPC.mjs";
|
|
7
|
+
import {
|
|
8
|
+
cn
|
|
9
|
+
} from "./chunk-LUZVQ3BX.mjs";
|
|
10
|
+
|
|
11
|
+
// src/components/DownloadButton/DownloadButton.tsx
|
|
12
|
+
import { useCallback, useState } from "react";
|
|
13
|
+
import { DownloadSimpleIcon } from "@phosphor-icons/react/dist/csr/DownloadSimple";
|
|
14
|
+
import JSZip from "jszip";
|
|
15
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
var getFileExtension = (url) => {
|
|
17
|
+
try {
|
|
18
|
+
const u = new URL(url, globalThis.location?.origin || "http://localhost");
|
|
19
|
+
url = u.pathname;
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
const path = url.split(/[?#]/)[0];
|
|
23
|
+
const dot = path.lastIndexOf(".");
|
|
24
|
+
return dot > -1 ? path.slice(dot + 1).toLowerCase() : "file";
|
|
25
|
+
};
|
|
26
|
+
var slugifyTitle = (lessonTitle) => lessonTitle.toLowerCase().replaceAll(/[^a-z0-9\s]/g, "").replaceAll(/\s+/g, "-").substring(0, 50);
|
|
27
|
+
var generateFilename = (contentType, url, lessonTitle = "aula") => `${slugifyTitle(lessonTitle)}-${contentType}.${getFileExtension(url)}`;
|
|
28
|
+
var getRemoteFileSize = async (url) => {
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(url, { method: "HEAD", mode: "cors" });
|
|
31
|
+
if (!response.ok) return 0;
|
|
32
|
+
return Number(response.headers.get("content-length")) || 0;
|
|
33
|
+
} catch {
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var fetchWithProgress = async (url, onBytes) => {
|
|
38
|
+
const response = await fetch(url, { mode: "cors" });
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Failed to fetch file: ${response.status} ${response.statusText}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (!response.body) {
|
|
45
|
+
const blob = await response.blob();
|
|
46
|
+
onBytes(blob.size);
|
|
47
|
+
return blob;
|
|
48
|
+
}
|
|
49
|
+
const reader = response.body.getReader();
|
|
50
|
+
const chunks = [];
|
|
51
|
+
for (; ; ) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done) break;
|
|
54
|
+
chunks.push(new Blob([value]));
|
|
55
|
+
onBytes(value.length);
|
|
56
|
+
}
|
|
57
|
+
return new Blob(chunks);
|
|
58
|
+
};
|
|
59
|
+
var saveBlob = (blob, filename) => {
|
|
60
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
61
|
+
const link = document.createElement("a");
|
|
62
|
+
link.href = blobUrl;
|
|
63
|
+
link.download = filename;
|
|
64
|
+
link.rel = "noopener noreferrer";
|
|
65
|
+
document.body.appendChild(link);
|
|
66
|
+
link.click();
|
|
67
|
+
link.remove();
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
URL.revokeObjectURL(blobUrl);
|
|
70
|
+
}, 1e3);
|
|
71
|
+
};
|
|
72
|
+
var DownloadButton = ({
|
|
73
|
+
content,
|
|
74
|
+
className,
|
|
75
|
+
onDownloadStart,
|
|
76
|
+
onDownloadComplete,
|
|
77
|
+
onDownloadError,
|
|
78
|
+
lessonTitle = "aula",
|
|
79
|
+
disabled = false
|
|
80
|
+
}) => {
|
|
81
|
+
const [isDownloading, setIsDownloading] = useState(false);
|
|
82
|
+
const [progress, setProgress] = useState(0);
|
|
83
|
+
const isValidUrl = useCallback((url) => {
|
|
84
|
+
return Boolean(
|
|
85
|
+
url && url.trim() !== "" && url !== "undefined" && url !== "null"
|
|
86
|
+
);
|
|
87
|
+
}, []);
|
|
88
|
+
const getAvailableContent = useCallback(() => {
|
|
89
|
+
const downloads = [];
|
|
90
|
+
if (isValidUrl(content.urlVideo)) {
|
|
91
|
+
downloads.push({ type: "video", url: content.urlVideo, label: "V\xEDdeo" });
|
|
92
|
+
}
|
|
93
|
+
if (isValidUrl(content.urlPodcast)) {
|
|
94
|
+
downloads.push({
|
|
95
|
+
type: "podcast",
|
|
96
|
+
url: content.urlPodcast,
|
|
97
|
+
label: "Podcast"
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (isValidUrl(content.urlInitialFrame)) {
|
|
101
|
+
downloads.push({
|
|
102
|
+
type: "quadro-inicial",
|
|
103
|
+
url: content.urlInitialFrame,
|
|
104
|
+
label: "Quadro Inicial"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (isValidUrl(content.urlFinalFrame)) {
|
|
108
|
+
downloads.push({
|
|
109
|
+
type: "quadro-final",
|
|
110
|
+
url: content.urlFinalFrame,
|
|
111
|
+
label: "Quadro Final"
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return downloads;
|
|
115
|
+
}, [content, isValidUrl]);
|
|
116
|
+
const handleDownload = useCallback(async () => {
|
|
117
|
+
if (disabled || isDownloading) return;
|
|
118
|
+
const availableContent = getAvailableContent();
|
|
119
|
+
if (availableContent.length === 0) return;
|
|
120
|
+
setIsDownloading(true);
|
|
121
|
+
setProgress(0);
|
|
122
|
+
try {
|
|
123
|
+
const sizes = await Promise.all(
|
|
124
|
+
availableContent.map((item) => getRemoteFileSize(item.url))
|
|
125
|
+
);
|
|
126
|
+
const totalBytes = sizes.reduce((sum, size) => sum + size, 0);
|
|
127
|
+
const useEqualWeights = totalBytes === 0;
|
|
128
|
+
const zip = new JSZip();
|
|
129
|
+
let downloadedBytes = 0;
|
|
130
|
+
let completedFiles = 0;
|
|
131
|
+
let zippedAny = false;
|
|
132
|
+
for (const [index, item] of availableContent.entries()) {
|
|
133
|
+
try {
|
|
134
|
+
onDownloadStart?.(item.type);
|
|
135
|
+
const blob = await fetchWithProgress(item.url, (chunkSize) => {
|
|
136
|
+
downloadedBytes += chunkSize;
|
|
137
|
+
if (!useEqualWeights) {
|
|
138
|
+
setProgress(
|
|
139
|
+
Math.min(99, Math.round(downloadedBytes / totalBytes * 100))
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
zip.file(generateFilename(item.type, item.url, lessonTitle), blob);
|
|
144
|
+
zippedAny = true;
|
|
145
|
+
onDownloadComplete?.(item.type);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
onDownloadError?.(
|
|
148
|
+
item.type,
|
|
149
|
+
error instanceof Error ? error : new Error(`Falha ao baixar ${item.label}`)
|
|
150
|
+
);
|
|
151
|
+
} finally {
|
|
152
|
+
completedFiles = index + 1;
|
|
153
|
+
if (useEqualWeights) {
|
|
154
|
+
setProgress(
|
|
155
|
+
Math.min(
|
|
156
|
+
99,
|
|
157
|
+
Math.round(completedFiles / availableContent.length * 100)
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!zippedAny) {
|
|
164
|
+
throw new Error("Nenhum conte\xFAdo da aula p\xF4de ser baixado");
|
|
165
|
+
}
|
|
166
|
+
const zipBlob = await zip.generateAsync({ type: "blob" });
|
|
167
|
+
saveBlob(zipBlob, `${slugifyTitle(lessonTitle)}.zip`);
|
|
168
|
+
setProgress(100);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
onDownloadError?.(
|
|
171
|
+
"aula",
|
|
172
|
+
error instanceof Error ? error : new Error("Falha ao baixar o conte\xFAdo da aula")
|
|
173
|
+
);
|
|
174
|
+
} finally {
|
|
175
|
+
setIsDownloading(false);
|
|
176
|
+
setProgress(0);
|
|
177
|
+
}
|
|
178
|
+
}, [
|
|
179
|
+
disabled,
|
|
180
|
+
isDownloading,
|
|
181
|
+
getAvailableContent,
|
|
182
|
+
lessonTitle,
|
|
183
|
+
onDownloadStart,
|
|
184
|
+
onDownloadComplete,
|
|
185
|
+
onDownloadError
|
|
186
|
+
]);
|
|
187
|
+
const availableCount = getAvailableContent().length;
|
|
188
|
+
if (availableCount === 0) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const suffix = availableCount > 1 ? "s" : "";
|
|
192
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center", className), children: [
|
|
193
|
+
/* @__PURE__ */ jsx(
|
|
194
|
+
IconButton_default,
|
|
195
|
+
{
|
|
196
|
+
icon: /* @__PURE__ */ jsx(DownloadSimpleIcon, { size: 24 }),
|
|
197
|
+
onClick: handleDownload,
|
|
198
|
+
disabled: disabled || isDownloading,
|
|
199
|
+
"aria-label": isDownloading ? "Baixando conte\xFAdo..." : `Baixar conte\xFAdo da aula (${availableCount} arquivo${suffix})`,
|
|
200
|
+
className: cn(
|
|
201
|
+
"!bg-transparent hover:!bg-black/10 transition-colors",
|
|
202
|
+
isDownloading && "opacity-60 cursor-not-allowed"
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
),
|
|
206
|
+
/* @__PURE__ */ jsx(
|
|
207
|
+
ProgressModal_default,
|
|
208
|
+
{
|
|
209
|
+
isOpen: isDownloading,
|
|
210
|
+
onClose: () => {
|
|
211
|
+
},
|
|
212
|
+
message: "Baixando conte\xFAdo da aula...",
|
|
213
|
+
progress
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
] });
|
|
217
|
+
};
|
|
218
|
+
var DownloadButton_default = DownloadButton;
|
|
219
|
+
|
|
220
|
+
export {
|
|
221
|
+
DownloadButton_default
|
|
222
|
+
};
|
|
223
|
+
//# sourceMappingURL=chunk-5L6CNJDJ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/DownloadButton/DownloadButton.tsx"],"sourcesContent":["import { useCallback, useState } from 'react';\nimport { DownloadSimpleIcon } from '@phosphor-icons/react/dist/csr/DownloadSimple';\nimport JSZip from 'jszip';\nimport IconButton from '../IconButton/IconButton';\nimport ProgressModal from '../ProgressModal/ProgressModal';\nimport { cn } from '../../utils/utils';\n\n/**\n * Download content interface for lesson materials\n */\nexport interface DownloadContent {\n /** Initial frame image URL */\n urlInitialFrame?: string;\n /** Final frame image URL */\n urlFinalFrame?: string;\n /** Podcast audio URL */\n urlPodcast?: string;\n /** Video URL */\n urlVideo?: string;\n}\n\n/**\n * Props for DownloadButton component\n */\nexport interface DownloadButtonProps {\n /** Content URLs to download */\n content: DownloadContent;\n /** Additional CSS classes */\n className?: string;\n /** Callback fired when download starts */\n onDownloadStart?: (contentType: string) => void;\n /** Callback fired when download completes */\n onDownloadComplete?: (contentType: string) => void;\n /** Callback fired when download fails */\n onDownloadError?: (contentType: string, error: Error) => void;\n /** Lesson title for download file naming */\n lessonTitle?: string;\n /** Whether the button is disabled */\n disabled?: boolean;\n}\n\n/** A single downloadable item resolved from the lesson content */\ninterface DownloadItem {\n type: string;\n url: string;\n label: string;\n}\n\n/**\n * Get file extension from URL\n * @param url - URL to extract extension from\n * @returns File extension or default\n */\nconst getFileExtension = (url: string): string => {\n try {\n const u = new URL(url, globalThis.location?.origin || 'http://localhost');\n url = u.pathname;\n } catch {\n // keep original url (likely relative)\n }\n const path = url.split(/[?#]/)[0];\n const dot = path.lastIndexOf('.');\n return dot > -1 ? path.slice(dot + 1).toLowerCase() : 'file';\n};\n\n/**\n * Slugify a lesson title for safe use in filenames\n * @param lessonTitle - Title of the lesson\n * @returns Sanitized slug\n */\nconst slugifyTitle = (lessonTitle: string): string =>\n lessonTitle\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s]/g, '')\n .replaceAll(/\\s+/g, '-')\n .substring(0, 50);\n\n/**\n * Generate the filename of an entry inside the zip\n * @param contentType - Type of content being downloaded\n * @param url - URL to get extension from\n * @param lessonTitle - Title of the lesson\n * @returns Generated filename\n */\nconst generateFilename = (\n contentType: string,\n url: string,\n lessonTitle: string = 'aula'\n): string =>\n `${slugifyTitle(lessonTitle)}-${contentType}.${getFileExtension(url)}`;\n\n/**\n * Read the byte size of a remote file without downloading it.\n *\n * Used to weight per-file progress so the bar advances proportionally to the\n * real payload (the video dwarfs the images). Returns 0 when the server omits\n * `content-length` or the request fails — callers fall back to equal weights.\n *\n * @param url - URL to probe\n * @returns Size in bytes, or 0 when unknown\n */\nconst getRemoteFileSize = async (url: string): Promise<number> => {\n try {\n const response = await fetch(url, { method: 'HEAD', mode: 'cors' });\n if (!response.ok) return 0;\n return Number(response.headers.get('content-length')) || 0;\n } catch {\n return 0;\n }\n};\n\n/**\n * Fetch a file as a Blob, reporting progress as bytes arrive.\n *\n * Streams the body so the caller sees real progress on the large files. When\n * the stream is unavailable (older browsers), falls back to a plain `.blob()`\n * and reports completion in one step.\n *\n * @param url - URL to download\n * @param onBytes - Called with the incremental byte count of each chunk\n * @returns The downloaded Blob\n */\nconst fetchWithProgress = async (\n url: string,\n onBytes: (chunkSize: number) => void\n): Promise<Blob> => {\n const response = await fetch(url, { mode: 'cors' });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`\n );\n }\n\n if (!response.body) {\n const blob = await response.blob();\n onBytes(blob.size);\n return blob;\n }\n\n const reader = response.body.getReader();\n // Each chunk is wrapped as a Blob so the array never has to be typed as\n // BlobPart[] — Uint8Array<ArrayBufferLike> does not satisfy it under TS 5.7.\n const chunks: Blob[] = [];\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(new Blob([value]));\n onBytes(value.length);\n }\n\n return new Blob(chunks);\n};\n\n/**\n * Save a Blob to disk through a single programmatic anchor click.\n *\n * Browsers block multiple programmatic downloads in a row, which is why the\n * whole lesson is bundled into one zip and saved with a single click.\n *\n * @param blob - Blob to save\n * @param filename - Filename for the download\n */\nconst saveBlob = (blob: Blob, filename: string): void => {\n const blobUrl = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = blobUrl;\n link.download = filename;\n link.rel = 'noopener noreferrer';\n\n document.body.appendChild(link);\n link.click();\n link.remove();\n\n setTimeout(() => {\n URL.revokeObjectURL(blobUrl);\n }, 1000);\n};\n\n/**\n * DownloadButton component for downloading lesson content\n *\n * Bundles every available lesson asset (video, podcast and board frames) into a\n * single zip and saves it in one click, mirroring the mobile app's \"baixar aula\n * completa\" behaviour. A progress modal reports the real byte progress.\n *\n * @param props - DownloadButton component props\n * @returns Download button element\n */\nconst DownloadButton = ({\n content,\n className,\n onDownloadStart,\n onDownloadComplete,\n onDownloadError,\n lessonTitle = 'aula',\n disabled = false,\n}: DownloadButtonProps) => {\n const [isDownloading, setIsDownloading] = useState(false);\n const [progress, setProgress] = useState(0);\n\n /**\n * Check if URL is valid and not empty\n * @param url - URL to validate\n * @returns Whether URL is valid\n */\n const isValidUrl = useCallback((url?: string): boolean => {\n return Boolean(\n url && url.trim() !== '' && url !== 'undefined' && url !== 'null'\n );\n }, []);\n\n /**\n * Get available download content\n * @returns Array of available download items\n */\n const getAvailableContent = useCallback((): DownloadItem[] => {\n const downloads: DownloadItem[] = [];\n\n if (isValidUrl(content.urlVideo)) {\n downloads.push({ type: 'video', url: content.urlVideo!, label: 'Vídeo' });\n }\n\n if (isValidUrl(content.urlPodcast)) {\n downloads.push({\n type: 'podcast',\n url: content.urlPodcast!,\n label: 'Podcast',\n });\n }\n\n if (isValidUrl(content.urlInitialFrame)) {\n downloads.push({\n type: 'quadro-inicial',\n url: content.urlInitialFrame!,\n label: 'Quadro Inicial',\n });\n }\n\n if (isValidUrl(content.urlFinalFrame)) {\n downloads.push({\n type: 'quadro-final',\n url: content.urlFinalFrame!,\n label: 'Quadro Final',\n });\n }\n\n return downloads;\n }, [content, isValidUrl]);\n\n /**\n * Bundle every available asset into a zip and save it in a single click.\n *\n * Assets are fetched sequentially so the progress bar tracks one file at a\n * time. A failed asset is reported through `onDownloadError` and skipped —\n * the zip still ships whatever downloaded successfully.\n */\n const handleDownload = useCallback(async () => {\n if (disabled || isDownloading) return;\n\n const availableContent = getAvailableContent();\n if (availableContent.length === 0) return;\n\n setIsDownloading(true);\n setProgress(0);\n\n try {\n const sizes = await Promise.all(\n availableContent.map((item) => getRemoteFileSize(item.url))\n );\n // Unknown sizes (server omitted content-length) fall back to equal weights.\n const totalBytes = sizes.reduce((sum, size) => sum + size, 0);\n const useEqualWeights = totalBytes === 0;\n\n const zip = new JSZip();\n let downloadedBytes = 0;\n let completedFiles = 0;\n let zippedAny = false;\n\n for (const [index, item] of availableContent.entries()) {\n try {\n onDownloadStart?.(item.type);\n\n const blob = await fetchWithProgress(item.url, (chunkSize) => {\n downloadedBytes += chunkSize;\n if (!useEqualWeights) {\n setProgress(\n Math.min(99, Math.round((downloadedBytes / totalBytes) * 100))\n );\n }\n });\n\n zip.file(generateFilename(item.type, item.url, lessonTitle), blob);\n zippedAny = true;\n\n onDownloadComplete?.(item.type);\n } catch (error) {\n onDownloadError?.(\n item.type,\n error instanceof Error\n ? error\n : new Error(`Falha ao baixar ${item.label}`)\n );\n } finally {\n completedFiles = index + 1;\n if (useEqualWeights) {\n setProgress(\n Math.min(\n 99,\n Math.round((completedFiles / availableContent.length) * 100)\n )\n );\n }\n }\n }\n\n if (!zippedAny) {\n throw new Error('Nenhum conteúdo da aula pôde ser baixado');\n }\n\n const zipBlob = await zip.generateAsync({ type: 'blob' });\n saveBlob(zipBlob, `${slugifyTitle(lessonTitle)}.zip`);\n setProgress(100);\n } catch (error) {\n onDownloadError?.(\n 'aula',\n error instanceof Error\n ? error\n : new Error('Falha ao baixar o conteúdo da aula')\n );\n } finally {\n setIsDownloading(false);\n setProgress(0);\n }\n }, [\n disabled,\n isDownloading,\n getAvailableContent,\n lessonTitle,\n onDownloadStart,\n onDownloadComplete,\n onDownloadError,\n ]);\n\n // Don't render if no content is available\n const availableCount = getAvailableContent().length;\n\n if (availableCount === 0) {\n return null;\n }\n\n const suffix = availableCount > 1 ? 's' : '';\n\n return (\n <div className={cn('flex items-center', className)}>\n <IconButton\n icon={<DownloadSimpleIcon size={24} />}\n onClick={handleDownload}\n disabled={disabled || isDownloading}\n aria-label={\n isDownloading\n ? 'Baixando conteúdo...'\n : `Baixar conteúdo da aula (${availableCount} arquivo${suffix})`\n }\n className={cn(\n '!bg-transparent hover:!bg-black/10 transition-colors',\n isDownloading && 'opacity-60 cursor-not-allowed'\n )}\n />\n\n <ProgressModal\n isOpen={isDownloading}\n onClose={() => {}}\n message=\"Baixando conteúdo da aula...\"\n progress={progress}\n />\n </div>\n );\n};\n\nexport default DownloadButton;\n"],"mappings":"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","names":[]}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./chunk-FD4UD2X5.mjs";
|
|
11
11
|
import {
|
|
12
12
|
VideoPlayer_default
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-INSEDDUA.mjs";
|
|
14
14
|
import {
|
|
15
15
|
Whiteboard_default
|
|
16
16
|
} from "./chunk-LSGYPHAO.mjs";
|
|
@@ -1721,4 +1721,4 @@ export {
|
|
|
1721
1721
|
StudentPerformanceModal,
|
|
1722
1722
|
RecommendedLessonDetails_default
|
|
1723
1723
|
};
|
|
1724
|
-
//# sourceMappingURL=chunk-
|
|
1724
|
+
//# sourceMappingURL=chunk-5SDXK6OI.mjs.map
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
var _chunkNDQLGQWQjs = require('./chunk-NDQLGQWQ.js');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
var _chunk472TMIRAjs = require('./chunk-472TMIRA.js');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
var _chunkKSB2HMD4js = require('./chunk-KSB2HMD4.js');
|
|
10
|
+
|
|
11
|
+
// src/components/DownloadButton/DownloadButton.tsx
|
|
12
|
+
var _react = require('react');
|
|
13
|
+
var _DownloadSimple = require('@phosphor-icons/react/dist/csr/DownloadSimple');
|
|
14
|
+
var _jszip = require('jszip'); var _jszip2 = _interopRequireDefault(_jszip);
|
|
15
|
+
var _jsxruntime = require('react/jsx-runtime');
|
|
16
|
+
var getFileExtension = (url) => {
|
|
17
|
+
try {
|
|
18
|
+
const u = new URL(url, _optionalChain([globalThis, 'access', _ => _.location, 'optionalAccess', _2 => _2.origin]) || "http://localhost");
|
|
19
|
+
url = u.pathname;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
}
|
|
22
|
+
const path = url.split(/[?#]/)[0];
|
|
23
|
+
const dot = path.lastIndexOf(".");
|
|
24
|
+
return dot > -1 ? path.slice(dot + 1).toLowerCase() : "file";
|
|
25
|
+
};
|
|
26
|
+
var slugifyTitle = (lessonTitle) => lessonTitle.toLowerCase().replaceAll(/[^a-z0-9\s]/g, "").replaceAll(/\s+/g, "-").substring(0, 50);
|
|
27
|
+
var generateFilename = (contentType, url, lessonTitle = "aula") => `${slugifyTitle(lessonTitle)}-${contentType}.${getFileExtension(url)}`;
|
|
28
|
+
var getRemoteFileSize = async (url) => {
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(url, { method: "HEAD", mode: "cors" });
|
|
31
|
+
if (!response.ok) return 0;
|
|
32
|
+
return Number(response.headers.get("content-length")) || 0;
|
|
33
|
+
} catch (e2) {
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var fetchWithProgress = async (url, onBytes) => {
|
|
38
|
+
const response = await fetch(url, { mode: "cors" });
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Failed to fetch file: ${response.status} ${response.statusText}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (!response.body) {
|
|
45
|
+
const blob = await response.blob();
|
|
46
|
+
onBytes(blob.size);
|
|
47
|
+
return blob;
|
|
48
|
+
}
|
|
49
|
+
const reader = response.body.getReader();
|
|
50
|
+
const chunks = [];
|
|
51
|
+
for (; ; ) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done) break;
|
|
54
|
+
chunks.push(new Blob([value]));
|
|
55
|
+
onBytes(value.length);
|
|
56
|
+
}
|
|
57
|
+
return new Blob(chunks);
|
|
58
|
+
};
|
|
59
|
+
var saveBlob = (blob, filename) => {
|
|
60
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
61
|
+
const link = document.createElement("a");
|
|
62
|
+
link.href = blobUrl;
|
|
63
|
+
link.download = filename;
|
|
64
|
+
link.rel = "noopener noreferrer";
|
|
65
|
+
document.body.appendChild(link);
|
|
66
|
+
link.click();
|
|
67
|
+
link.remove();
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
URL.revokeObjectURL(blobUrl);
|
|
70
|
+
}, 1e3);
|
|
71
|
+
};
|
|
72
|
+
var DownloadButton = ({
|
|
73
|
+
content,
|
|
74
|
+
className,
|
|
75
|
+
onDownloadStart,
|
|
76
|
+
onDownloadComplete,
|
|
77
|
+
onDownloadError,
|
|
78
|
+
lessonTitle = "aula",
|
|
79
|
+
disabled = false
|
|
80
|
+
}) => {
|
|
81
|
+
const [isDownloading, setIsDownloading] = _react.useState.call(void 0, false);
|
|
82
|
+
const [progress, setProgress] = _react.useState.call(void 0, 0);
|
|
83
|
+
const isValidUrl = _react.useCallback.call(void 0, (url) => {
|
|
84
|
+
return Boolean(
|
|
85
|
+
url && url.trim() !== "" && url !== "undefined" && url !== "null"
|
|
86
|
+
);
|
|
87
|
+
}, []);
|
|
88
|
+
const getAvailableContent = _react.useCallback.call(void 0, () => {
|
|
89
|
+
const downloads = [];
|
|
90
|
+
if (isValidUrl(content.urlVideo)) {
|
|
91
|
+
downloads.push({ type: "video", url: content.urlVideo, label: "V\xEDdeo" });
|
|
92
|
+
}
|
|
93
|
+
if (isValidUrl(content.urlPodcast)) {
|
|
94
|
+
downloads.push({
|
|
95
|
+
type: "podcast",
|
|
96
|
+
url: content.urlPodcast,
|
|
97
|
+
label: "Podcast"
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (isValidUrl(content.urlInitialFrame)) {
|
|
101
|
+
downloads.push({
|
|
102
|
+
type: "quadro-inicial",
|
|
103
|
+
url: content.urlInitialFrame,
|
|
104
|
+
label: "Quadro Inicial"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (isValidUrl(content.urlFinalFrame)) {
|
|
108
|
+
downloads.push({
|
|
109
|
+
type: "quadro-final",
|
|
110
|
+
url: content.urlFinalFrame,
|
|
111
|
+
label: "Quadro Final"
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return downloads;
|
|
115
|
+
}, [content, isValidUrl]);
|
|
116
|
+
const handleDownload = _react.useCallback.call(void 0, async () => {
|
|
117
|
+
if (disabled || isDownloading) return;
|
|
118
|
+
const availableContent = getAvailableContent();
|
|
119
|
+
if (availableContent.length === 0) return;
|
|
120
|
+
setIsDownloading(true);
|
|
121
|
+
setProgress(0);
|
|
122
|
+
try {
|
|
123
|
+
const sizes = await Promise.all(
|
|
124
|
+
availableContent.map((item) => getRemoteFileSize(item.url))
|
|
125
|
+
);
|
|
126
|
+
const totalBytes = sizes.reduce((sum, size) => sum + size, 0);
|
|
127
|
+
const useEqualWeights = totalBytes === 0;
|
|
128
|
+
const zip = new (0, _jszip2.default)();
|
|
129
|
+
let downloadedBytes = 0;
|
|
130
|
+
let completedFiles = 0;
|
|
131
|
+
let zippedAny = false;
|
|
132
|
+
for (const [index, item] of availableContent.entries()) {
|
|
133
|
+
try {
|
|
134
|
+
_optionalChain([onDownloadStart, 'optionalCall', _3 => _3(item.type)]);
|
|
135
|
+
const blob = await fetchWithProgress(item.url, (chunkSize) => {
|
|
136
|
+
downloadedBytes += chunkSize;
|
|
137
|
+
if (!useEqualWeights) {
|
|
138
|
+
setProgress(
|
|
139
|
+
Math.min(99, Math.round(downloadedBytes / totalBytes * 100))
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
zip.file(generateFilename(item.type, item.url, lessonTitle), blob);
|
|
144
|
+
zippedAny = true;
|
|
145
|
+
_optionalChain([onDownloadComplete, 'optionalCall', _4 => _4(item.type)]);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
_optionalChain([onDownloadError, 'optionalCall', _5 => _5(
|
|
148
|
+
item.type,
|
|
149
|
+
error instanceof Error ? error : new Error(`Falha ao baixar ${item.label}`)
|
|
150
|
+
)]);
|
|
151
|
+
} finally {
|
|
152
|
+
completedFiles = index + 1;
|
|
153
|
+
if (useEqualWeights) {
|
|
154
|
+
setProgress(
|
|
155
|
+
Math.min(
|
|
156
|
+
99,
|
|
157
|
+
Math.round(completedFiles / availableContent.length * 100)
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!zippedAny) {
|
|
164
|
+
throw new Error("Nenhum conte\xFAdo da aula p\xF4de ser baixado");
|
|
165
|
+
}
|
|
166
|
+
const zipBlob = await zip.generateAsync({ type: "blob" });
|
|
167
|
+
saveBlob(zipBlob, `${slugifyTitle(lessonTitle)}.zip`);
|
|
168
|
+
setProgress(100);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
_optionalChain([onDownloadError, 'optionalCall', _6 => _6(
|
|
171
|
+
"aula",
|
|
172
|
+
error instanceof Error ? error : new Error("Falha ao baixar o conte\xFAdo da aula")
|
|
173
|
+
)]);
|
|
174
|
+
} finally {
|
|
175
|
+
setIsDownloading(false);
|
|
176
|
+
setProgress(0);
|
|
177
|
+
}
|
|
178
|
+
}, [
|
|
179
|
+
disabled,
|
|
180
|
+
isDownloading,
|
|
181
|
+
getAvailableContent,
|
|
182
|
+
lessonTitle,
|
|
183
|
+
onDownloadStart,
|
|
184
|
+
onDownloadComplete,
|
|
185
|
+
onDownloadError
|
|
186
|
+
]);
|
|
187
|
+
const availableCount = getAvailableContent().length;
|
|
188
|
+
if (availableCount === 0) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const suffix = availableCount > 1 ? "s" : "";
|
|
192
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKSB2HMD4js.cn.call(void 0, "flex items-center", className), children: [
|
|
193
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
194
|
+
_chunk472TMIRAjs.IconButton_default,
|
|
195
|
+
{
|
|
196
|
+
icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _DownloadSimple.DownloadSimpleIcon, { size: 24 }),
|
|
197
|
+
onClick: handleDownload,
|
|
198
|
+
disabled: disabled || isDownloading,
|
|
199
|
+
"aria-label": isDownloading ? "Baixando conte\xFAdo..." : `Baixar conte\xFAdo da aula (${availableCount} arquivo${suffix})`,
|
|
200
|
+
className: _chunkKSB2HMD4js.cn.call(void 0,
|
|
201
|
+
"!bg-transparent hover:!bg-black/10 transition-colors",
|
|
202
|
+
isDownloading && "opacity-60 cursor-not-allowed"
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
),
|
|
206
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
207
|
+
_chunkNDQLGQWQjs.ProgressModal_default,
|
|
208
|
+
{
|
|
209
|
+
isOpen: isDownloading,
|
|
210
|
+
onClose: () => {
|
|
211
|
+
},
|
|
212
|
+
message: "Baixando conte\xFAdo da aula...",
|
|
213
|
+
progress
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
] });
|
|
217
|
+
};
|
|
218
|
+
var DownloadButton_default = DownloadButton;
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
exports.DownloadButton_default = DownloadButton_default;
|
|
223
|
+
//# sourceMappingURL=chunk-GWEXMU4A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-GWEXMU4A.js","../src/components/DownloadButton/DownloadButton.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-GWEXMU4A.js","sourcesContent":[null,"import { useCallback, useState } from 'react';\nimport { DownloadSimpleIcon } from '@phosphor-icons/react/dist/csr/DownloadSimple';\nimport JSZip from 'jszip';\nimport IconButton from '../IconButton/IconButton';\nimport ProgressModal from '../ProgressModal/ProgressModal';\nimport { cn } from '../../utils/utils';\n\n/**\n * Download content interface for lesson materials\n */\nexport interface DownloadContent {\n /** Initial frame image URL */\n urlInitialFrame?: string;\n /** Final frame image URL */\n urlFinalFrame?: string;\n /** Podcast audio URL */\n urlPodcast?: string;\n /** Video URL */\n urlVideo?: string;\n}\n\n/**\n * Props for DownloadButton component\n */\nexport interface DownloadButtonProps {\n /** Content URLs to download */\n content: DownloadContent;\n /** Additional CSS classes */\n className?: string;\n /** Callback fired when download starts */\n onDownloadStart?: (contentType: string) => void;\n /** Callback fired when download completes */\n onDownloadComplete?: (contentType: string) => void;\n /** Callback fired when download fails */\n onDownloadError?: (contentType: string, error: Error) => void;\n /** Lesson title for download file naming */\n lessonTitle?: string;\n /** Whether the button is disabled */\n disabled?: boolean;\n}\n\n/** A single downloadable item resolved from the lesson content */\ninterface DownloadItem {\n type: string;\n url: string;\n label: string;\n}\n\n/**\n * Get file extension from URL\n * @param url - URL to extract extension from\n * @returns File extension or default\n */\nconst getFileExtension = (url: string): string => {\n try {\n const u = new URL(url, globalThis.location?.origin || 'http://localhost');\n url = u.pathname;\n } catch {\n // keep original url (likely relative)\n }\n const path = url.split(/[?#]/)[0];\n const dot = path.lastIndexOf('.');\n return dot > -1 ? path.slice(dot + 1).toLowerCase() : 'file';\n};\n\n/**\n * Slugify a lesson title for safe use in filenames\n * @param lessonTitle - Title of the lesson\n * @returns Sanitized slug\n */\nconst slugifyTitle = (lessonTitle: string): string =>\n lessonTitle\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s]/g, '')\n .replaceAll(/\\s+/g, '-')\n .substring(0, 50);\n\n/**\n * Generate the filename of an entry inside the zip\n * @param contentType - Type of content being downloaded\n * @param url - URL to get extension from\n * @param lessonTitle - Title of the lesson\n * @returns Generated filename\n */\nconst generateFilename = (\n contentType: string,\n url: string,\n lessonTitle: string = 'aula'\n): string =>\n `${slugifyTitle(lessonTitle)}-${contentType}.${getFileExtension(url)}`;\n\n/**\n * Read the byte size of a remote file without downloading it.\n *\n * Used to weight per-file progress so the bar advances proportionally to the\n * real payload (the video dwarfs the images). Returns 0 when the server omits\n * `content-length` or the request fails — callers fall back to equal weights.\n *\n * @param url - URL to probe\n * @returns Size in bytes, or 0 when unknown\n */\nconst getRemoteFileSize = async (url: string): Promise<number> => {\n try {\n const response = await fetch(url, { method: 'HEAD', mode: 'cors' });\n if (!response.ok) return 0;\n return Number(response.headers.get('content-length')) || 0;\n } catch {\n return 0;\n }\n};\n\n/**\n * Fetch a file as a Blob, reporting progress as bytes arrive.\n *\n * Streams the body so the caller sees real progress on the large files. When\n * the stream is unavailable (older browsers), falls back to a plain `.blob()`\n * and reports completion in one step.\n *\n * @param url - URL to download\n * @param onBytes - Called with the incremental byte count of each chunk\n * @returns The downloaded Blob\n */\nconst fetchWithProgress = async (\n url: string,\n onBytes: (chunkSize: number) => void\n): Promise<Blob> => {\n const response = await fetch(url, { mode: 'cors' });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`\n );\n }\n\n if (!response.body) {\n const blob = await response.blob();\n onBytes(blob.size);\n return blob;\n }\n\n const reader = response.body.getReader();\n // Each chunk is wrapped as a Blob so the array never has to be typed as\n // BlobPart[] — Uint8Array<ArrayBufferLike> does not satisfy it under TS 5.7.\n const chunks: Blob[] = [];\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(new Blob([value]));\n onBytes(value.length);\n }\n\n return new Blob(chunks);\n};\n\n/**\n * Save a Blob to disk through a single programmatic anchor click.\n *\n * Browsers block multiple programmatic downloads in a row, which is why the\n * whole lesson is bundled into one zip and saved with a single click.\n *\n * @param blob - Blob to save\n * @param filename - Filename for the download\n */\nconst saveBlob = (blob: Blob, filename: string): void => {\n const blobUrl = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = blobUrl;\n link.download = filename;\n link.rel = 'noopener noreferrer';\n\n document.body.appendChild(link);\n link.click();\n link.remove();\n\n setTimeout(() => {\n URL.revokeObjectURL(blobUrl);\n }, 1000);\n};\n\n/**\n * DownloadButton component for downloading lesson content\n *\n * Bundles every available lesson asset (video, podcast and board frames) into a\n * single zip and saves it in one click, mirroring the mobile app's \"baixar aula\n * completa\" behaviour. A progress modal reports the real byte progress.\n *\n * @param props - DownloadButton component props\n * @returns Download button element\n */\nconst DownloadButton = ({\n content,\n className,\n onDownloadStart,\n onDownloadComplete,\n onDownloadError,\n lessonTitle = 'aula',\n disabled = false,\n}: DownloadButtonProps) => {\n const [isDownloading, setIsDownloading] = useState(false);\n const [progress, setProgress] = useState(0);\n\n /**\n * Check if URL is valid and not empty\n * @param url - URL to validate\n * @returns Whether URL is valid\n */\n const isValidUrl = useCallback((url?: string): boolean => {\n return Boolean(\n url && url.trim() !== '' && url !== 'undefined' && url !== 'null'\n );\n }, []);\n\n /**\n * Get available download content\n * @returns Array of available download items\n */\n const getAvailableContent = useCallback((): DownloadItem[] => {\n const downloads: DownloadItem[] = [];\n\n if (isValidUrl(content.urlVideo)) {\n downloads.push({ type: 'video', url: content.urlVideo!, label: 'Vídeo' });\n }\n\n if (isValidUrl(content.urlPodcast)) {\n downloads.push({\n type: 'podcast',\n url: content.urlPodcast!,\n label: 'Podcast',\n });\n }\n\n if (isValidUrl(content.urlInitialFrame)) {\n downloads.push({\n type: 'quadro-inicial',\n url: content.urlInitialFrame!,\n label: 'Quadro Inicial',\n });\n }\n\n if (isValidUrl(content.urlFinalFrame)) {\n downloads.push({\n type: 'quadro-final',\n url: content.urlFinalFrame!,\n label: 'Quadro Final',\n });\n }\n\n return downloads;\n }, [content, isValidUrl]);\n\n /**\n * Bundle every available asset into a zip and save it in a single click.\n *\n * Assets are fetched sequentially so the progress bar tracks one file at a\n * time. A failed asset is reported through `onDownloadError` and skipped —\n * the zip still ships whatever downloaded successfully.\n */\n const handleDownload = useCallback(async () => {\n if (disabled || isDownloading) return;\n\n const availableContent = getAvailableContent();\n if (availableContent.length === 0) return;\n\n setIsDownloading(true);\n setProgress(0);\n\n try {\n const sizes = await Promise.all(\n availableContent.map((item) => getRemoteFileSize(item.url))\n );\n // Unknown sizes (server omitted content-length) fall back to equal weights.\n const totalBytes = sizes.reduce((sum, size) => sum + size, 0);\n const useEqualWeights = totalBytes === 0;\n\n const zip = new JSZip();\n let downloadedBytes = 0;\n let completedFiles = 0;\n let zippedAny = false;\n\n for (const [index, item] of availableContent.entries()) {\n try {\n onDownloadStart?.(item.type);\n\n const blob = await fetchWithProgress(item.url, (chunkSize) => {\n downloadedBytes += chunkSize;\n if (!useEqualWeights) {\n setProgress(\n Math.min(99, Math.round((downloadedBytes / totalBytes) * 100))\n );\n }\n });\n\n zip.file(generateFilename(item.type, item.url, lessonTitle), blob);\n zippedAny = true;\n\n onDownloadComplete?.(item.type);\n } catch (error) {\n onDownloadError?.(\n item.type,\n error instanceof Error\n ? error\n : new Error(`Falha ao baixar ${item.label}`)\n );\n } finally {\n completedFiles = index + 1;\n if (useEqualWeights) {\n setProgress(\n Math.min(\n 99,\n Math.round((completedFiles / availableContent.length) * 100)\n )\n );\n }\n }\n }\n\n if (!zippedAny) {\n throw new Error('Nenhum conteúdo da aula pôde ser baixado');\n }\n\n const zipBlob = await zip.generateAsync({ type: 'blob' });\n saveBlob(zipBlob, `${slugifyTitle(lessonTitle)}.zip`);\n setProgress(100);\n } catch (error) {\n onDownloadError?.(\n 'aula',\n error instanceof Error\n ? error\n : new Error('Falha ao baixar o conteúdo da aula')\n );\n } finally {\n setIsDownloading(false);\n setProgress(0);\n }\n }, [\n disabled,\n isDownloading,\n getAvailableContent,\n lessonTitle,\n onDownloadStart,\n onDownloadComplete,\n onDownloadError,\n ]);\n\n // Don't render if no content is available\n const availableCount = getAvailableContent().length;\n\n if (availableCount === 0) {\n return null;\n }\n\n const suffix = availableCount > 1 ? 's' : '';\n\n return (\n <div className={cn('flex items-center', className)}>\n <IconButton\n icon={<DownloadSimpleIcon size={24} />}\n onClick={handleDownload}\n disabled={disabled || isDownloading}\n aria-label={\n isDownloading\n ? 'Baixando conteúdo...'\n : `Baixar conteúdo da aula (${availableCount} arquivo${suffix})`\n }\n className={cn(\n '!bg-transparent hover:!bg-black/10 transition-colors',\n isDownloading && 'opacity-60 cursor-not-allowed'\n )}\n />\n\n <ProgressModal\n isOpen={isDownloading}\n onClose={() => {}}\n message=\"Baixando conteúdo da aula...\"\n progress={progress}\n />\n </div>\n );\n};\n\nexport default DownloadButton;\n"]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-GKY77WVA.mjs";
|
|
4
4
|
import {
|
|
5
5
|
DownloadButton_default
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-5L6CNJDJ.mjs";
|
|
7
7
|
import {
|
|
8
8
|
IconButton_default
|
|
9
9
|
} from "./chunk-YP3VXXPC.mjs";
|
|
@@ -914,4 +914,4 @@ var VideoPlayer_default = VideoPlayer;
|
|
|
914
914
|
export {
|
|
915
915
|
VideoPlayer_default
|
|
916
916
|
};
|
|
917
|
-
//# sourceMappingURL=chunk-
|
|
917
|
+
//# sourceMappingURL=chunk-INSEDDUA.mjs.map
|
|
@@ -138,7 +138,7 @@ var _chunkVDLACBCFjs = require('./chunk-VDLACBCF.js');
|
|
|
138
138
|
var _chunkFRT4G7KFjs = require('./chunk-FRT4G7KF.js');
|
|
139
139
|
|
|
140
140
|
|
|
141
|
-
var
|
|
141
|
+
var _chunkO3XVL3WEjs = require('./chunk-O3XVL3WE.js');
|
|
142
142
|
|
|
143
143
|
|
|
144
144
|
var _chunkZHWRUIVEjs = require('./chunk-ZHWRUIVE.js');
|
|
@@ -4882,7 +4882,7 @@ var VideoSection = ({
|
|
|
4882
4882
|
}
|
|
4883
4883
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
4884
4884
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
4885
|
-
|
|
4885
|
+
_chunkO3XVL3WEjs.VideoPlayer_default,
|
|
4886
4886
|
{
|
|
4887
4887
|
src: videoData.src,
|
|
4888
4888
|
poster: videoData.poster,
|
|
@@ -19907,4 +19907,4 @@ var CheckboxGroup = ({
|
|
|
19907
19907
|
|
|
19908
19908
|
|
|
19909
19909
|
exports.CheckboxGroup = CheckboxGroup; exports.DateTimeInput_default = DateTimeInput_default; exports.AlertsManager = AlertsManager; exports.createUseActivityFiltersData = createUseActivityFiltersData; exports.createActivityFiltersDataHook = createActivityFiltersDataHook; exports.convertActivityFiltersToQuestionsFilter = convertActivityFiltersToQuestionsFilter; exports.ActivityListQuestions = ActivityListQuestions; exports.CreateActivity = CreateActivity; exports.ActivityCardQuestionPreview = ActivityCardQuestionPreview; exports.ChooseActivityModelModal = ChooseActivityModelModal; exports.LessonPreview = LessonPreview; exports.useLessonFiltersStore = useLessonFiltersStore; exports.RecommendedLessonCreate = RecommendedLessonCreate; exports.FILTER_CATEGORY = FILTER_CATEGORY; exports.FILTER_GROUP = FILTER_GROUP; exports.FilterModal = FilterModal; exports.useUserAccessData = useUserAccessData; exports.useStudentsFilter = useStudentsFilter; exports.StudentsFilterSection = StudentsFilterSection; exports.SimulatedFiltersModal = SimulatedFiltersModal; exports.useSimulatedSubjects = useSimulatedSubjects; exports.SimulatedSubjectMenu = SimulatedSubjectMenu; exports.SimulatedRankingCard = SimulatedRankingCard; exports.SimulatedStudentRanking = SimulatedStudentRanking; exports.PerformanceDistributionChart = PerformanceDistributionChart; exports.TableProvider = TableProvider; exports.TableProvider_default = TableProvider_default; exports.useSimulatedContentDetails = useSimulatedContentDetails; exports.SimulatedContentDetailsModal = SimulatedContentDetailsModal; exports.useEssayCompetenciesOverview = useEssayCompetenciesOverview; exports.useEssayCompetenceDetails = useEssayCompetenceDetails; exports.EssayCompetenceDetailsModal = EssayCompetenceDetailsModal; exports.EssayCompetenciesTable = EssayCompetenciesTable; exports.useEssayStudentDetails = useEssayStudentDetails; exports.EssayStudentDetailsModal = EssayStudentDetailsModal; exports.useQuestionFiltersStore = useQuestionFiltersStore; exports.ActivityFilters = ActivityFilters; exports.ActivityFiltersPopover = ActivityFiltersPopover; exports.FillInBlanks = FillInBlanks; exports.ConnectDots = ConnectDots; exports.useAppContent = useAppContent; exports.ActivityCardQuestionBanks = ActivityCardQuestionBanks; exports.QuestionsPdfContent = QuestionsPdfContent; exports.useQuestionsPdfPrint = useQuestionsPdfPrint; exports.QuestionsPdfGenerator = QuestionsPdfGenerator; exports.ActivityPreview = ActivityPreview; exports.HierarchicalCheckboxGroup_default = HierarchicalCheckboxGroup_default; exports.useActivityDetails = useActivityDetails; exports.ActivityDetails = ActivityDetails; exports.ActivityDetails_default = ActivityDetails_default; exports.SendActivityModal_default = SendActivityModal_default; exports.SaveActivityModelModal = SaveActivityModelModal; exports.useSendLessonModal = useSendLessonModal; exports.SendLessonModal_default = SendLessonModal_default; exports.RecommendedClassDraftsTab = RecommendedClassDraftsTab; exports.RecommendedClassPageTab = RecommendedClassPageTab; exports.RecommendedLessonsHistory = RecommendedLessonsHistory; exports.RecommendedLessonsHistory_default = RecommendedLessonsHistory_default; exports.recommendedClassApiResponseSchema = recommendedClassApiResponseSchema; exports.recommendedClassDetailsApiResponseSchema = recommendedClassDetailsApiResponseSchema; exports.historyApiResponseSchema = historyApiResponseSchema; exports.handleLessonDetailsFetchError = handleLessonDetailsFetchError; exports.createUseRecommendedLessonDetails = createUseRecommendedLessonDetails; exports.createRecommendedLessonDetailsHook = createRecommendedLessonDetailsHook; exports.ActivitiesHistory = ActivitiesHistory; exports.ActivitiesHistory_default = ActivitiesHistory_default; exports.ActivityTab = ActivityTab; exports.ActivityPageLayout = ActivityPageLayout; exports.ExamTab = ExamTab; exports.ExamPageLayout = ExamPageLayout; exports.UnifiedHistoryPage = UnifiedHistoryPage; exports.createExamDraftsModelsTableColumns = createExamDraftsModelsTableColumns; exports.createDraftsModelsFiltersConfig = createDraftsModelsFiltersConfig; exports.useActivityDraftModelPage = useActivityDraftModelPage; exports.UnifiedDraftModelPage = UnifiedDraftModelPage; exports.ChatLoading = ChatLoading; exports.createUseForum = createUseForum; exports.DEFAULT_PERIOD_SELECTION = DEFAULT_PERIOD_SELECTION; exports.getPeriodSelectionLabel = getPeriodSelectionLabel; exports.PeriodSelectorModal = PeriodSelectorModal; exports.PrintableUsersTable_default = PrintableUsersTable_default; exports.SimulatedPerformanceView = SimulatedPerformanceView; exports.SimulationsDetailModal = SimulationsDetailModal; exports.SimulationsPage = SimulationsPage; exports.AnswerSheetPreview = AnswerSheetPreview; exports.AnswerSheetsBatchPreview = AnswerSheetsBatchPreview; exports.ExamDetailsHeader = ExamDetailsHeader; exports.formatQuestions = formatQuestions; exports.ExamStatsCards = ExamStatsCards; exports.getExamStudentStatusBadgeAction = getExamStudentStatusBadgeAction; exports.getExamStudentStatusDisplayText = getExamStudentStatusDisplayText; exports.createExamStudentsTableColumns = createExamStudentsTableColumns; exports.ExamStudentsTable = ExamStudentsTable; exports.ExamDetailsPage = ExamDetailsPage; exports.ExamDetailsPage_default = ExamDetailsPage_default;
|
|
19910
|
-
//# sourceMappingURL=chunk-
|
|
19910
|
+
//# sourceMappingURL=chunk-LJEGZ7RW.js.map
|