documentation-hub 5.7.2
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/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- package/vite.config.ts +152 -0
package/Updater.md
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# Implementing delta updates in Electron with electron-builder and electron-updater
|
|
2
|
+
|
|
3
|
+
Delta updates work out-of-the-box with your stack—**blockmap files are generated automatically** for NSIS targets, and electron-updater uses them by default. The key is ensuring proper configuration for auto-restart and handling your corporate network scenario. Here's everything you need for a complete implementation.
|
|
4
|
+
|
|
5
|
+
## How blockmap-based delta updates work
|
|
6
|
+
|
|
7
|
+
When electron-builder creates an NSIS installer, it automatically generates a `.blockmap` file alongside the `.exe`. This file contains checksums of content-defined chunks created using a rolling hash algorithm. During updates, electron-updater downloads both the old and new blockmap files, compares them, and only downloads the changed blocks via HTTP Range requests. A typical update might show: **"Full: 66,133 KB, To download: 7,785 KB (12%)"**—demonstrating significant bandwidth savings.
|
|
8
|
+
|
|
9
|
+
Three files are generated for each build: the installer (`App Setup 1.0.0.exe`), its blockmap (`App Setup 1.0.0.exe.blockmap`), and the metadata file (`latest.yml`). All three must be published to GitHub Releases for delta updates to function.
|
|
10
|
+
|
|
11
|
+
## Package.json build configuration
|
|
12
|
+
|
|
13
|
+
No special configuration is required to enable delta updates—they're on by default. However, your `artifactName` must include the version to ensure proper blockmap URL construction:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"name": "your-app",
|
|
18
|
+
"version": "1.0.0",
|
|
19
|
+
"build": {
|
|
20
|
+
"appId": "com.yourcompany.app",
|
|
21
|
+
"productName": "Your App",
|
|
22
|
+
"win": {
|
|
23
|
+
"target": "nsis",
|
|
24
|
+
"icon": "build/icon.ico"
|
|
25
|
+
},
|
|
26
|
+
"nsis": {
|
|
27
|
+
"oneClick": true,
|
|
28
|
+
"perMachine": false,
|
|
29
|
+
"artifactName": "${productName} Setup ${version}.${ext}",
|
|
30
|
+
"deleteAppDataOnUninstall": false
|
|
31
|
+
},
|
|
32
|
+
"publish": {
|
|
33
|
+
"provider": "github",
|
|
34
|
+
"owner": "your-username",
|
|
35
|
+
"repo": "your-repo",
|
|
36
|
+
"releaseType": "release"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"electron-updater": "^6.7.2",
|
|
41
|
+
"electron-log": "^5.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"electron": "^28.0.0",
|
|
45
|
+
"electron-builder": "^26.3.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
To explicitly disable delta updates (if needed), add `"differentialPackage": false` under the `nsis` key, or set `autoUpdater.disableDifferentialDownload = true` at runtime.
|
|
51
|
+
|
|
52
|
+
## Complete auto-update implementation with silent restart
|
|
53
|
+
|
|
54
|
+
Here's a production-ready implementation that handles your Zscaler/corporate network scenario with automatic silent restart:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// updater.ts
|
|
58
|
+
import { NsisUpdater, UpdateInfo, ProgressInfo } from 'electron-updater';
|
|
59
|
+
import { app, session, BrowserWindow } from 'electron';
|
|
60
|
+
import log from 'electron-log';
|
|
61
|
+
|
|
62
|
+
export class AutoUpdateManager {
|
|
63
|
+
private autoUpdater: NsisUpdater;
|
|
64
|
+
private mainWindow: BrowserWindow | null = null;
|
|
65
|
+
|
|
66
|
+
constructor(mainWindow?: BrowserWindow) {
|
|
67
|
+
this.mainWindow = mainWindow || null;
|
|
68
|
+
|
|
69
|
+
// Use NsisUpdater directly for custom configuration
|
|
70
|
+
this.autoUpdater = new NsisUpdater({
|
|
71
|
+
provider: 'github',
|
|
72
|
+
owner: 'your-username',
|
|
73
|
+
repo: 'your-repo'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.configureUpdater();
|
|
77
|
+
this.setupCorporateNetworkHandling();
|
|
78
|
+
this.setupEventHandlers();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private configureUpdater(): void {
|
|
82
|
+
// Attach logger for debugging
|
|
83
|
+
this.autoUpdater.logger = log;
|
|
84
|
+
log.transports.file.level = 'debug';
|
|
85
|
+
|
|
86
|
+
// Core configuration
|
|
87
|
+
this.autoUpdater.autoDownload = true; // Auto-download when found
|
|
88
|
+
this.autoUpdater.autoInstallOnAppQuit = true; // Install on quit as fallback
|
|
89
|
+
this.autoUpdater.autoRunAppAfterInstall = true; // Restart after install
|
|
90
|
+
|
|
91
|
+
// Keep differential downloads enabled (default)
|
|
92
|
+
this.autoUpdater.disableDifferentialDownload = false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private setupCorporateNetworkHandling(): void {
|
|
96
|
+
// Handle Zscaler/corporate proxy certificate interception
|
|
97
|
+
session.defaultSession.setCertificateVerifyProc((request, callback) => {
|
|
98
|
+
const { hostname, certificate, verificationResult } = request;
|
|
99
|
+
|
|
100
|
+
log.debug('Certificate verification:', {
|
|
101
|
+
hostname,
|
|
102
|
+
issuer: certificate.issuerName,
|
|
103
|
+
result: verificationResult
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Accept known corporate CA certificates (Zscaler, etc.)
|
|
107
|
+
const trustedIssuers = ['Zscaler', 'YourCorpCA'];
|
|
108
|
+
const isTrustedCorporate = trustedIssuers.some(
|
|
109
|
+
issuer => certificate.issuerName.includes(issuer)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (verificationResult === 'net::OK' || isTrustedCorporate) {
|
|
113
|
+
callback(0); // Accept
|
|
114
|
+
} else {
|
|
115
|
+
log.warn('Rejecting untrusted certificate:', certificate.issuerName);
|
|
116
|
+
callback(-2); // Reject
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private setupEventHandlers(): void {
|
|
122
|
+
this.autoUpdater.on('checking-for-update', () => {
|
|
123
|
+
log.info('Checking for update...');
|
|
124
|
+
this.sendStatusToRenderer('checking');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.autoUpdater.on('update-available', (info: UpdateInfo) => {
|
|
128
|
+
log.info(`Update available: ${info.version}`);
|
|
129
|
+
this.sendStatusToRenderer('available', info);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
this.autoUpdater.on('update-not-available', (info: UpdateInfo) => {
|
|
133
|
+
log.info('Application is up to date');
|
|
134
|
+
this.sendStatusToRenderer('not-available', info);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
this.autoUpdater.on('download-progress', (progress: ProgressInfo) => {
|
|
138
|
+
const message = `Downloaded ${progress.percent.toFixed(1)}% (${this.formatBytes(progress.transferred)}/${this.formatBytes(progress.total)})`;
|
|
139
|
+
log.info(message);
|
|
140
|
+
this.sendStatusToRenderer('downloading', progress);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
this.autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
|
|
144
|
+
log.info(`Update downloaded: ${info.version}`);
|
|
145
|
+
this.sendStatusToRenderer('downloaded', info);
|
|
146
|
+
|
|
147
|
+
// Trigger silent automatic restart
|
|
148
|
+
this.installAndRestart();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
this.autoUpdater.on('error', (error: Error) => {
|
|
152
|
+
log.error('Auto-updater error:', error);
|
|
153
|
+
this.sendStatusToRenderer('error', { message: error.message });
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private installAndRestart(): void {
|
|
158
|
+
log.info('Installing update and restarting...');
|
|
159
|
+
|
|
160
|
+
setImmediate(() => {
|
|
161
|
+
// Remove listeners that might prevent quit
|
|
162
|
+
app.removeAllListeners('window-all-closed');
|
|
163
|
+
|
|
164
|
+
// quitAndInstall(isSilent, isForceRunAfter)
|
|
165
|
+
// isSilent=true: No installer UI shown
|
|
166
|
+
// isForceRunAfter=true: App restarts after silent install
|
|
167
|
+
this.autoUpdater.quitAndInstall(true, true);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private sendStatusToRenderer(status: string, data?: any): void {
|
|
172
|
+
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
|
173
|
+
this.mainWindow.webContents.send('update-status', { status, data });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private formatBytes(bytes: number): string {
|
|
178
|
+
const mb = bytes / (1024 * 1024);
|
|
179
|
+
return `${mb.toFixed(1)} MB`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public async checkForUpdates(): Promise<void> {
|
|
183
|
+
// Skip check on first run (Squirrel issue)
|
|
184
|
+
if (process.argv.includes('--squirrel-firstrun')) {
|
|
185
|
+
log.info('Skipping update check on first run');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
await this.autoUpdater.checkForUpdates();
|
|
191
|
+
} catch (error) {
|
|
192
|
+
log.error('Update check failed:', error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public startScheduledChecks(intervalMs: number = 4 * 60 * 60 * 1000): void {
|
|
197
|
+
// Check every 4 hours by default
|
|
198
|
+
setInterval(() => this.checkForUpdates(), intervalMs);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Main process integration
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// main.ts
|
|
207
|
+
import { app, BrowserWindow } from 'electron';
|
|
208
|
+
import { AutoUpdateManager } from './updater';
|
|
209
|
+
|
|
210
|
+
let mainWindow: BrowserWindow | null = null;
|
|
211
|
+
let updateManager: AutoUpdateManager;
|
|
212
|
+
|
|
213
|
+
app.whenReady().then(() => {
|
|
214
|
+
mainWindow = new BrowserWindow({
|
|
215
|
+
width: 1200,
|
|
216
|
+
height: 800,
|
|
217
|
+
webPreferences: {
|
|
218
|
+
nodeIntegration: false,
|
|
219
|
+
contextIsolation: true,
|
|
220
|
+
preload: path.join(__dirname, 'preload.js')
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Initialize updater after window is ready
|
|
225
|
+
updateManager = new AutoUpdateManager(mainWindow);
|
|
226
|
+
|
|
227
|
+
// Delay initial check to avoid startup conflicts
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
updateManager.checkForUpdates();
|
|
230
|
+
}, 10000);
|
|
231
|
+
|
|
232
|
+
// Start periodic checks
|
|
233
|
+
updateManager.startScheduledChecks();
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Testing delta updates locally
|
|
238
|
+
|
|
239
|
+
Create a `dev-app-update.yml` file in your project root to test updates against a local server:
|
|
240
|
+
|
|
241
|
+
```yaml
|
|
242
|
+
provider: generic
|
|
243
|
+
url: http://localhost:8080/updates/
|
|
244
|
+
channel: latest
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Set up a simple local update server:
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
// local-server.js
|
|
251
|
+
const express = require('express');
|
|
252
|
+
const path = require('path');
|
|
253
|
+
const app = express();
|
|
254
|
+
|
|
255
|
+
// Enable range requests for differential downloads
|
|
256
|
+
app.use('/updates', express.static(path.join(__dirname, 'dist'), {
|
|
257
|
+
acceptRanges: true
|
|
258
|
+
}));
|
|
259
|
+
|
|
260
|
+
app.listen(8080, () => console.log('Update server: http://localhost:8080'));
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Modify your updater to use the dev config during development:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import path from 'path';
|
|
267
|
+
|
|
268
|
+
// In your updater constructor, add:
|
|
269
|
+
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
|
|
270
|
+
this.autoUpdater.forceDevUpdateConfig = true;
|
|
271
|
+
this.autoUpdater.updateConfigPath = path.join(__dirname, '../dev-app-update.yml');
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Testing workflow:**
|
|
276
|
+
1. Build version 1.0.0: `npm run build`
|
|
277
|
+
2. Copy dist files to your server directory
|
|
278
|
+
3. Install and run version 1.0.0
|
|
279
|
+
4. Bump version to 1.0.1 and rebuild
|
|
280
|
+
5. Copy new dist files (including `.blockmap`) to server
|
|
281
|
+
6. The running app should detect and delta-download the update
|
|
282
|
+
|
|
283
|
+
## Verifying differential downloads actually work
|
|
284
|
+
|
|
285
|
+
Enable debug logging and look for these key log entries that confirm delta updates:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
[info] Download block maps (old: ".../1.0.0.exe.blockmap", new: ".../1.0.1.exe.blockmap")
|
|
289
|
+
[info] File has 367 changed blocks
|
|
290
|
+
[info] Full: 113,665.86 KB, To download: 7,785.66 KB (7%)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
If you see **"Cannot download differentially, fallback to full download"**, check that:
|
|
294
|
+
- Both `.blockmap` files are accessible (current and new version)
|
|
295
|
+
- Your server supports HTTP Range requests
|
|
296
|
+
- The SHA512 checksums in `latest.yml` match the actual files
|
|
297
|
+
- The updater cache isn't corrupted (clear `%AppData%\Local\{app-name}-updater`)
|
|
298
|
+
|
|
299
|
+
## Troubleshooting common issues
|
|
300
|
+
|
|
301
|
+
**Blockmap 404 errors on GitHub**: Ensure you publish both the `.exe` and `.exe.blockmap` files to the same release. When using `electron-builder --publish always`, this happens automatically.
|
|
302
|
+
|
|
303
|
+
**Certificate errors behind Zscaler**: The `setCertificateVerifyProc` handler in the implementation above addresses this. Add your corporate CA's issuer name to the `trustedIssuers` array.
|
|
304
|
+
|
|
305
|
+
**Silent install not restarting**: The `quitAndInstall(true, true)` call with both parameters set to `true` forces silent install with auto-restart. Ensure `app.removeAllListeners('window-all-closed')` is called before `quitAndInstall` to prevent quit interruption.
|
|
306
|
+
|
|
307
|
+
**First-run update check fails**: Windows Squirrel holds a file lock during initial installation. Always check for `--squirrel-firstrun` in process arguments and skip the update check on first launch, as shown in the implementation.
|
|
308
|
+
|
|
309
|
+
## Conclusion
|
|
310
|
+
|
|
311
|
+
The electron-builder + electron-updater stack provides robust delta update support with minimal configuration. Key takeaways: blockmaps are generated automatically for NSIS targets, `quitAndInstall(true, true)` enables silent auto-restart on Windows, and corporate proxy scenarios require explicit certificate handling via `setCertificateVerifyProc`. For local testing, combine `dev-app-update.yml` with `forceDevUpdateConfig = true` and a local HTTP server with range request support. Monitor your logs for the "changed blocks" message to confirm differential downloads are working—you should see download sizes of **5-20% of the full installer** for typical application updates.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { createCanvas, loadImage } = require('canvas');
|
|
4
|
+
|
|
5
|
+
// Convert PNG to ICO format for Windows
|
|
6
|
+
function createICOFromPNG(pngPath, icoPath, sizes = [16, 32, 48, 256]) {
|
|
7
|
+
return new Promise(async (resolve, reject) => {
|
|
8
|
+
try {
|
|
9
|
+
const sourceImage = await loadImage(pngPath);
|
|
10
|
+
const iconData = [];
|
|
11
|
+
|
|
12
|
+
// Generate each size
|
|
13
|
+
for (const size of sizes) {
|
|
14
|
+
const canvas = createCanvas(size, size);
|
|
15
|
+
const ctx = canvas.getContext('2d');
|
|
16
|
+
|
|
17
|
+
// Enable high-quality image scaling
|
|
18
|
+
ctx.imageSmoothingEnabled = true;
|
|
19
|
+
ctx.imageSmoothingQuality = 'high';
|
|
20
|
+
|
|
21
|
+
// Draw resized image
|
|
22
|
+
ctx.drawImage(sourceImage, 0, 0, size, size);
|
|
23
|
+
|
|
24
|
+
// Get RGBA pixel data
|
|
25
|
+
const imageData = ctx.getImageData(0, 0, size, size);
|
|
26
|
+
const pixels = Buffer.alloc(size * size * 4);
|
|
27
|
+
|
|
28
|
+
// Convert RGBA to BGRA and flip vertically (BMP stores rows bottom-to-top)
|
|
29
|
+
const rowBytes = size * 4;
|
|
30
|
+
for (let y = 0; y < size; y++) {
|
|
31
|
+
const srcRow = y * rowBytes;
|
|
32
|
+
const dstRow = (size - 1 - y) * rowBytes;
|
|
33
|
+
for (let x = 0; x < size; x++) {
|
|
34
|
+
const srcIdx = srcRow + x * 4;
|
|
35
|
+
const dstIdx = dstRow + x * 4;
|
|
36
|
+
pixels[dstIdx] = imageData.data[srcIdx + 2]; // B
|
|
37
|
+
pixels[dstIdx + 1] = imageData.data[srcIdx + 1]; // G
|
|
38
|
+
pixels[dstIdx + 2] = imageData.data[srcIdx]; // R
|
|
39
|
+
pixels[dstIdx + 3] = imageData.data[srcIdx + 3]; // A
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
iconData.push({ size, data: pixels, imageData });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ICO header
|
|
47
|
+
const header = Buffer.alloc(6);
|
|
48
|
+
header.writeUInt16LE(0, 0); // Reserved
|
|
49
|
+
header.writeUInt16LE(1, 2); // Type (1 = ICO)
|
|
50
|
+
header.writeUInt16LE(iconData.length, 4); // Number of images
|
|
51
|
+
|
|
52
|
+
// Calculate offsets
|
|
53
|
+
let offset = 6 + (16 * iconData.length);
|
|
54
|
+
const directories = [];
|
|
55
|
+
const images = [];
|
|
56
|
+
|
|
57
|
+
iconData.forEach(icon => {
|
|
58
|
+
// Directory entry
|
|
59
|
+
const dir = Buffer.alloc(16);
|
|
60
|
+
dir[0] = icon.size < 256 ? icon.size : 0; // Width
|
|
61
|
+
dir[1] = icon.size < 256 ? icon.size : 0; // Height
|
|
62
|
+
dir[2] = 0; // Color palette
|
|
63
|
+
dir[3] = 0; // Reserved
|
|
64
|
+
dir.writeUInt16LE(1, 4); // Color planes
|
|
65
|
+
dir.writeUInt16LE(32, 6); // Bits per pixel
|
|
66
|
+
|
|
67
|
+
// BMP data size = header(40) + pixels + mask (DWORD-aligned rows)
|
|
68
|
+
const maskRowSize = Math.ceil(Math.ceil(icon.size / 8) / 4) * 4;
|
|
69
|
+
const bmpSize = 40 + icon.data.length + (maskRowSize * icon.size);
|
|
70
|
+
dir.writeUInt32LE(bmpSize, 8);
|
|
71
|
+
dir.writeUInt32LE(offset, 12);
|
|
72
|
+
|
|
73
|
+
directories.push(dir);
|
|
74
|
+
|
|
75
|
+
// BMP header
|
|
76
|
+
const bmpHeader = Buffer.alloc(40);
|
|
77
|
+
bmpHeader.writeInt32LE(40, 0); // Header size
|
|
78
|
+
bmpHeader.writeInt32LE(icon.size, 4); // Width
|
|
79
|
+
bmpHeader.writeInt32LE(icon.size * 2, 8); // Height (doubled for ICO)
|
|
80
|
+
bmpHeader.writeUInt16LE(1, 12); // Planes
|
|
81
|
+
bmpHeader.writeUInt16LE(32, 14); // Bits per pixel
|
|
82
|
+
bmpHeader.writeUInt32LE(0, 16); // Compression
|
|
83
|
+
bmpHeader.writeUInt32LE(icon.data.length, 20); // Image size
|
|
84
|
+
|
|
85
|
+
// Generate AND mask from alpha channel
|
|
86
|
+
// ICO AND mask: 1 = transparent, 0 = opaque
|
|
87
|
+
// Rows must be DWORD (4-byte) aligned
|
|
88
|
+
const rowSize = Math.ceil(icon.size / 8);
|
|
89
|
+
const alignedRowSize = Math.ceil(rowSize / 4) * 4;
|
|
90
|
+
const mask = Buffer.alloc(alignedRowSize * icon.size, 0);
|
|
91
|
+
|
|
92
|
+
// BMP stores rows bottom-up, so we need to flip vertically
|
|
93
|
+
for (let y = 0; y < icon.size; y++) {
|
|
94
|
+
const srcRow = icon.size - 1 - y; // Flip vertically for BMP format
|
|
95
|
+
for (let x = 0; x < icon.size; x++) {
|
|
96
|
+
const pixelIndex = (srcRow * icon.size + x) * 4;
|
|
97
|
+
const alpha = icon.imageData.data[pixelIndex + 3];
|
|
98
|
+
|
|
99
|
+
// If pixel is transparent (alpha < 128), set mask bit to 1
|
|
100
|
+
if (alpha < 128) {
|
|
101
|
+
const byteIndex = y * alignedRowSize + Math.floor(x / 8);
|
|
102
|
+
const bitIndex = 7 - (x % 8); // MSB first within each byte
|
|
103
|
+
mask[byteIndex] |= (1 << bitIndex);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
images.push(Buffer.concat([bmpHeader, icon.data, mask]));
|
|
109
|
+
offset += bmpSize;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Combine all parts
|
|
113
|
+
const ico = Buffer.concat([header, ...directories, ...images]);
|
|
114
|
+
fs.writeFileSync(icoPath, ico);
|
|
115
|
+
console.log(`✓ Created ${path.basename(icoPath)}: ${fs.statSync(icoPath).size.toLocaleString()} bytes`);
|
|
116
|
+
resolve();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
reject(error);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Resize PNG image to specified size
|
|
124
|
+
async function resizePNG(sourcePath, targetPath, targetSize) {
|
|
125
|
+
try {
|
|
126
|
+
const sourceImage = await loadImage(sourcePath);
|
|
127
|
+
const canvas = createCanvas(targetSize, targetSize);
|
|
128
|
+
const ctx = canvas.getContext('2d');
|
|
129
|
+
|
|
130
|
+
// Enable high-quality image scaling
|
|
131
|
+
ctx.imageSmoothingEnabled = true;
|
|
132
|
+
ctx.imageSmoothingQuality = 'high';
|
|
133
|
+
|
|
134
|
+
// Draw resized image
|
|
135
|
+
ctx.drawImage(sourceImage, 0, 0, targetSize, targetSize);
|
|
136
|
+
|
|
137
|
+
// Save as PNG
|
|
138
|
+
const buffer = canvas.toBuffer('image/png');
|
|
139
|
+
fs.writeFileSync(targetPath, buffer);
|
|
140
|
+
console.log(`✓ Created ${path.basename(targetPath)}: ${targetSize}x${targetSize} (${fs.statSync(targetPath).size.toLocaleString()} bytes)`);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(`✗ Error creating ${path.basename(targetPath)}:`, error.message);
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Main execution
|
|
148
|
+
async function generateIcons() {
|
|
149
|
+
console.log('='.repeat(60));
|
|
150
|
+
console.log('Generating application icons from icon_1024x1024.png');
|
|
151
|
+
console.log('='.repeat(60));
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// Determine if we're running from the build directory or root
|
|
155
|
+
const isInBuildDir = __dirname.endsWith('build');
|
|
156
|
+
const rootDir = isInBuildDir ? path.join(__dirname, '..') : __dirname;
|
|
157
|
+
const buildDir = path.join(rootDir, 'build');
|
|
158
|
+
const sourcePath = path.join(buildDir, 'icon_1024x1024.png');
|
|
159
|
+
|
|
160
|
+
// Verify source file exists
|
|
161
|
+
if (!fs.existsSync(sourcePath)) {
|
|
162
|
+
throw new Error(`Source file not found: ${sourcePath}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log(`\nSource: ${path.basename(sourcePath)}`);
|
|
166
|
+
console.log(`Build directory: ${buildDir}`);
|
|
167
|
+
|
|
168
|
+
// Ensure build directory exists
|
|
169
|
+
if (!fs.existsSync(buildDir)) {
|
|
170
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
171
|
+
console.log('✓ Created build directory');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log('\nGenerating icon files...\n');
|
|
175
|
+
|
|
176
|
+
// Copy original as base icon.png (electron-builder will use this)
|
|
177
|
+
const baseIconPath = path.join(buildDir, 'icon.png');
|
|
178
|
+
fs.copyFileSync(sourcePath, baseIconPath);
|
|
179
|
+
console.log(`+ Copied source to icon.png: 1024x1024`);
|
|
180
|
+
|
|
181
|
+
// Generate standard sizes
|
|
182
|
+
await resizePNG(sourcePath, path.join(buildDir, '512x512.png'), 512);
|
|
183
|
+
await resizePNG(sourcePath, path.join(buildDir, '256x256.png'), 256);
|
|
184
|
+
|
|
185
|
+
// Generate Windows ICO file
|
|
186
|
+
console.log('\nGenerating Windows ICO file...\n');
|
|
187
|
+
await createICOFromPNG(sourcePath, path.join(buildDir, 'icon.ico'));
|
|
188
|
+
|
|
189
|
+
console.log('\n' + '='.repeat(60));
|
|
190
|
+
console.log('✅ Icons generated successfully!');
|
|
191
|
+
console.log('='.repeat(60));
|
|
192
|
+
console.log('\nGenerated files:');
|
|
193
|
+
console.log(' - icon.png (1024x1024) - Base icon for electron-builder');
|
|
194
|
+
console.log(' - 512x512.png - Linux icon');
|
|
195
|
+
console.log(' - 256x256.png - Linux icon');
|
|
196
|
+
console.log(' - icon.ico - Windows application icon');
|
|
197
|
+
console.log('\nElectron-builder will auto-generate:');
|
|
198
|
+
console.log(' - .icns for macOS from icon.png');
|
|
199
|
+
console.log(' - Additional Windows sizes if needed');
|
|
200
|
+
console.log('');
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error('\n❌ Error generating icons:', error.message);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Run the script
|
|
208
|
+
generateIcons();
|
package/build/icon.ico
ADDED
|
Binary file
|
package/build/icon.png
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a6 as I,a7 as K,a8 as B,r as U,j as e,m as n,l as S,ad as R,aY as W,aZ as E,ab as T}from"./vendor-ui-BU7NfluV.js";import{r as i}from"./vendor-react-BVZ_anCF.js";import{C as o,a as c,b as p,c as u,d as j}from"./Card-IAZin8kp.js";import{a as V,c as M,B as Y}from"./index-8xUe8ptc.js";import{R as y,L,C as v,X as g,Y as k,T as b,a as f,b as H,B as $,d as A}from"./vendor-charts-RkGK5ROP.js";const G={hidden:{opacity:0},visible:{opacity:1,transition:{staggerChildren:.1}}},d={hidden:{opacity:0,y:20},visible:{opacity:1,y:0,transition:{duration:.5}}},_=i.memo(function(){const[r,F]=i.useState("daily"),[t,N]=i.useState(null),{stats:l,getDailyHistory:C,getWeeklyHistory:D,getMonthlyHistory:w}=V(),m=i.useCallback(a=>{a?.activePayload?.[0]?.payload&&N(a.activePayload[0].payload)},[]),h=i.useCallback(()=>{N(null)},[]),x=i.useMemo(()=>r==="daily"?[...C(30)].reverse().map(s=>({date:new Date(s.date).toLocaleDateString("en-US",{month:"short",day:"numeric"}),Documents:s.documentsProcessed,Hyperlinks:s.hyperlinksChecked,Feedback:s.feedbackImported,"Time (min)":s.timeSaved})):r==="weekly"?[...D(12)].reverse().map(s=>({date:`${new Date(s.weekStart).toLocaleDateString("en-US",{month:"short",day:"numeric"})} - ${new Date(s.weekEnd).toLocaleDateString("en-US",{month:"short",day:"numeric"})}`,Documents:s.documentsProcessed,Hyperlinks:s.hyperlinksChecked,Feedback:s.feedbackImported,"Time (min)":s.timeSaved})):[...w(12)].reverse().map(s=>({date:new Date(s.month+"-01").toLocaleDateString("en-US",{month:"short",year:"numeric"}),Documents:s.documentsProcessed,Hyperlinks:s.hyperlinksChecked,Feedback:s.feedbackImported,"Time (min)":s.timeSaved})),[r,C,D,w]),P=[{value:"daily",label:"Daily",icon:R,description:"Last 30 days"},{value:"weekly",label:"Weekly",icon:W,description:"Last 12 weeks"},{value:"monthly",label:"Monthly",icon:E,description:"Last 12 months"}],z=[{title:"Documents Processed",value:l.allTime.documentsProcessed,icon:I,color:"text-green-500",bgColor:"bg-green-500/10"},{title:"Hyperlinks Checked",value:l.allTime.hyperlinksChecked,icon:K,color:"text-blue-500",bgColor:"bg-blue-500/10"},{title:"Feedback Imported",value:l.allTime.feedbackImported,icon:B,color:"text-purple-500",bgColor:"bg-purple-500/10"},{title:"Time Saved",value:`${l.allTime.timeSaved}m`,icon:U,color:"text-orange-500",bgColor:"bg-orange-500/10"}];return e.jsxs(n.div,{variants:G,initial:"hidden",animate:"visible",className:"p-6 space-y-6 max-w-[1400px] mx-auto",children:[e.jsxs("div",{className:"sticky top-0 z-10 bg-background -mx-6 px-6 pb-4 border-b border-border/50",children:[e.jsx(n.div,{variants:d,className:"flex items-center justify-between pt-0 pb-4",children:e.jsxs("div",{className:"flex items-center gap-4",children:[e.jsxs("div",{children:[e.jsxs("h1",{className:"text-3xl font-bold mb-1 flex items-center gap-2",children:[e.jsx(S,{className:"w-8 h-8"}),"Analytics"]}),e.jsx("p",{className:"text-muted-foreground",children:"Track performance and insights"})]}),e.jsx("div",{className:"h-12 w-px bg-border"}),t?e.jsxs("div",{className:"text-base font-medium flex items-center gap-4",children:[e.jsxs("span",{className:"text-muted-foreground",children:[t.date,":"]}),t.Documents!==void 0&&e.jsxs("span",{className:"text-green-500",children:["Docs: ",t.Documents]}),t.Hyperlinks!==void 0&&e.jsxs("span",{className:"text-blue-500",children:["Links: ",t.Hyperlinks]}),t.Feedback!==void 0&&e.jsxs("span",{className:"text-purple-500",children:["Feedback: ",t.Feedback]}),t["Time (min)"]!==void 0&&e.jsxs("span",{className:"text-orange-500",children:["Time: ",t["Time (min)"],"m"]})]}):e.jsx("span",{className:"text-muted-foreground italic text-sm",children:"Hover over charts for details"})]})}),e.jsx(n.div,{variants:d,className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4",children:z.map(a=>{const s=a.icon;return e.jsx(o,{className:"border-border/50",children:e.jsx(c,{className:"pt-6",children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-sm text-foreground mb-1",children:a.title}),e.jsx("p",{className:"text-2xl font-bold",children:a.value})]}),e.jsx("div",{className:M("p-3 rounded-lg",a.bgColor),children:e.jsx(s,{className:M("w-6 h-6",a.color)})})]})})},a.title)})})]}),e.jsx(n.div,{variants:d,className:"flex gap-2",children:P.map(a=>{const s=a.icon;return e.jsxs(Y,{variant:r===a.value?"default":"outline",onClick:()=>F(a.value),className:"flex-1 gap-2",children:[e.jsx(s,{className:"w-4 h-4"}),e.jsxs("div",{className:"flex flex-col items-start",children:[e.jsx("span",{className:"font-semibold",children:a.label}),e.jsx("span",{className:"text-xs opacity-70",children:a.description})]})]},a.value)})}),e.jsxs(n.div,{variants:d,className:"grid grid-cols-1 gap-6",children:[e.jsxs(o,{children:[e.jsxs(p,{children:[e.jsxs(u,{className:"flex items-center gap-2",children:[e.jsx(T,{className:"w-5 h-5"}),"Documents Processed Over Time"]}),e.jsxs(j,{children:["Track document processing trends across"," ",r==="daily"?"days":r==="weekly"?"weeks":"months"]})]}),e.jsx(c,{children:e.jsx(y,{width:"100%",height:300,children:e.jsxs(L,{data:x,onMouseMove:m,onMouseLeave:h,children:[e.jsx(v,{strokeDasharray:"3 3",className:"stroke-border"}),e.jsx(g,{dataKey:"date",tick:{fontSize:12,fill:"var(--color-foreground)"},angle:-45,textAnchor:"end",height:80}),e.jsx(k,{tick:{fontSize:12,fill:"var(--color-foreground)"}}),e.jsx(b,{content:()=>null}),e.jsx(f,{}),e.jsx(H,{type:"monotone",dataKey:"Documents",stroke:"hsl(142, 76%, 36%)",strokeWidth:2,dot:{r:4}})]})})})]}),e.jsxs(o,{children:[e.jsxs(p,{children:[e.jsxs(u,{className:"flex items-center gap-2",children:[e.jsx(T,{className:"w-5 h-5"}),"Hyperlinks Checked Over Time"]}),e.jsxs(j,{children:["Track hyperlink validation trends across"," ",r==="daily"?"days":r==="weekly"?"weeks":"months"]})]}),e.jsx(c,{children:e.jsx(y,{width:"100%",height:300,children:e.jsxs(L,{data:x,onMouseMove:m,onMouseLeave:h,children:[e.jsx(v,{strokeDasharray:"3 3",className:"stroke-border"}),e.jsx(g,{dataKey:"date",tick:{fontSize:12,fill:"var(--color-foreground)"},angle:-45,textAnchor:"end",height:80}),e.jsx(k,{tick:{fontSize:12,fill:"var(--color-foreground)"}}),e.jsx(b,{content:()=>null}),e.jsx(f,{}),e.jsx(H,{type:"monotone",dataKey:"Hyperlinks",stroke:"hsl(221, 83%, 53%)",strokeWidth:2,dot:{r:4}})]})})})]}),e.jsxs(o,{children:[e.jsxs(p,{children:[e.jsxs(u,{className:"flex items-center gap-2",children:[e.jsx(S,{className:"w-5 h-5"}),"Activity Breakdown"]}),e.jsx(j,{children:"Compare different metrics side by side"})]}),e.jsx(c,{children:e.jsx(y,{width:"100%",height:300,children:e.jsxs($,{data:x,onMouseMove:m,onMouseLeave:h,children:[e.jsx(v,{strokeDasharray:"3 3",className:"stroke-border"}),e.jsx(g,{dataKey:"date",tick:{fontSize:12,fill:"var(--color-foreground)"},angle:-45,textAnchor:"end",height:80}),e.jsx(k,{tick:{fontSize:12,fill:"var(--color-foreground)"}}),e.jsx(b,{content:()=>null}),e.jsx(f,{}),e.jsx(A,{dataKey:"Feedback",fill:"hsl(280, 100%, 70%)"}),e.jsx(A,{dataKey:"Time (min)",fill:"hsl(25, 95%, 53%)"})]})})})]})]})]})});export{_ as Analytics};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{j as s,m as c}from"./vendor-ui-BU7NfluV.js";import{r as o}from"./vendor-react-BVZ_anCF.js";import{c as d}from"./index-8xUe8ptc.js";const m=o.forwardRef(({className:r,variant:e="default",interactive:a=!1,children:t,...i},n)=>{const l={default:"bg-card text-card-foreground shadow-xs",bordered:"border border-border bg-card text-card-foreground",ghost:"bg-transparent",glass:"glass border border-white/10 shadow-lg"};return s.jsx(c.div,{ref:n,className:d("rounded-lg",l[e],a&&"cursor-pointer transition-shadow duration-200 hover:shadow-md",r),whileHover:a?{scale:1.02}:void 0,whileTap:a?{scale:.98}:void 0,transition:{duration:.2},...i,children:t})});m.displayName="Card";const f=o.forwardRef(({className:r,...e},a)=>s.jsx("div",{ref:a,className:d("flex flex-col space-y-1.5 p-6",r),...e}));f.displayName="CardHeader";const p=o.forwardRef(({className:r,...e},a)=>s.jsx("h3",{ref:a,className:d("text-2xl font-semibold leading-none tracking-tight",r),...e}));p.displayName="CardTitle";const x=o.forwardRef(({className:r,...e},a)=>s.jsx("p",{ref:a,className:d("text-sm text-muted-foreground",r),...e}));x.displayName="CardDescription";const C=o.forwardRef(({className:r,...e},a)=>s.jsx("div",{ref:a,className:d("p-6 pt-0",r),...e}));C.displayName="CardContent";const g=o.forwardRef(({className:r,...e},a)=>s.jsx("div",{ref:a,className:d("flex items-center p-6 pt-0",r),...e}));g.displayName="CardFooter";export{m as C,C as a,f as b,p as c,x as d};
|