markupr 2.1.8
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/.claude/commands/review-feedback.md +47 -0
- package/.eslintrc.json +35 -0
- package/.github/CODEOWNERS +16 -0
- package/.github/FUNDING.yml +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +56 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +54 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +89 -0
- package/.github/dependabot.yml +70 -0
- package/.github/workflows/ci.yml +184 -0
- package/.github/workflows/deploy-landing.yml +134 -0
- package/.github/workflows/nightly.yml +288 -0
- package/.github/workflows/release.yml +318 -0
- package/CHANGELOG.md +127 -0
- package/CLAUDE.md +137 -0
- package/CODE_OF_CONDUCT.md +9 -0
- package/CONTRIBUTING.md +390 -0
- package/LICENSE +21 -0
- package/PRODUCT_VISION.md +277 -0
- package/README.md +517 -0
- package/SECURITY.md +51 -0
- package/SIGNING_INSTRUCTIONS.md +284 -0
- package/assets/DMG_BACKGROUND_INSTRUCTIONS.md +130 -0
- package/assets/svg-source/dmg-background.svg +70 -0
- package/assets/svg-source/icon.svg +20 -0
- package/assets/svg-source/tray-icon-processing.svg +7 -0
- package/assets/svg-source/tray-icon-recording.svg +7 -0
- package/assets/svg-source/tray-icon.svg +6 -0
- package/assets/tray-complete.png +0 -0
- package/assets/tray-complete@2x.png +0 -0
- package/assets/tray-completeTemplate.png +0 -0
- package/assets/tray-completeTemplate@2x.png +0 -0
- package/assets/tray-error.png +0 -0
- package/assets/tray-error@2x.png +0 -0
- package/assets/tray-errorTemplate.png +0 -0
- package/assets/tray-errorTemplate@2x.png +0 -0
- package/assets/tray-icon-processing.png +0 -0
- package/assets/tray-icon-processing@2x.png +0 -0
- package/assets/tray-icon-processingTemplate.png +0 -0
- package/assets/tray-icon-processingTemplate@2x.png +0 -0
- package/assets/tray-icon-recording.png +0 -0
- package/assets/tray-icon-recording@2x.png +0 -0
- package/assets/tray-icon-recordingTemplate.png +0 -0
- package/assets/tray-icon-recordingTemplate@2x.png +0 -0
- package/assets/tray-icon.png +0 -0
- package/assets/tray-icon@2x.png +0 -0
- package/assets/tray-iconTemplate.png +0 -0
- package/assets/tray-iconTemplate@2x.png +0 -0
- package/assets/tray-idle.png +0 -0
- package/assets/tray-idle@2x.png +0 -0
- package/assets/tray-idleTemplate.png +0 -0
- package/assets/tray-idleTemplate@2x.png +0 -0
- package/assets/tray-processing-0.png +0 -0
- package/assets/tray-processing-0@2x.png +0 -0
- package/assets/tray-processing-0Template.png +0 -0
- package/assets/tray-processing-0Template@2x.png +0 -0
- package/assets/tray-processing-1.png +0 -0
- package/assets/tray-processing-1@2x.png +0 -0
- package/assets/tray-processing-1Template.png +0 -0
- package/assets/tray-processing-1Template@2x.png +0 -0
- package/assets/tray-processing-2.png +0 -0
- package/assets/tray-processing-2@2x.png +0 -0
- package/assets/tray-processing-2Template.png +0 -0
- package/assets/tray-processing-2Template@2x.png +0 -0
- package/assets/tray-processing-3.png +0 -0
- package/assets/tray-processing-3@2x.png +0 -0
- package/assets/tray-processing-3Template.png +0 -0
- package/assets/tray-processing-3Template@2x.png +0 -0
- package/assets/tray-processing.png +0 -0
- package/assets/tray-processing@2x.png +0 -0
- package/assets/tray-processingTemplate.png +0 -0
- package/assets/tray-processingTemplate@2x.png +0 -0
- package/assets/tray-recording.png +0 -0
- package/assets/tray-recording@2x.png +0 -0
- package/assets/tray-recordingTemplate.png +0 -0
- package/assets/tray-recordingTemplate@2x.png +0 -0
- package/build/DMG_BACKGROUND_SPEC.md +50 -0
- package/build/dmg-background.png +0 -0
- package/build/dmg-background@2x.png +0 -0
- package/build/entitlements.mac.inherit.plist +27 -0
- package/build/entitlements.mac.plist +41 -0
- package/build/favicon-16.png +0 -0
- package/build/favicon-180.png +0 -0
- package/build/favicon-192.png +0 -0
- package/build/favicon-32.png +0 -0
- package/build/favicon-48.png +0 -0
- package/build/favicon-512.png +0 -0
- package/build/favicon-64.png +0 -0
- package/build/icon-128.png +0 -0
- package/build/icon-16.png +0 -0
- package/build/icon-24.png +0 -0
- package/build/icon-256.png +0 -0
- package/build/icon-32.png +0 -0
- package/build/icon-48.png +0 -0
- package/build/icon-64.png +0 -0
- package/build/icon.icns +0 -0
- package/build/icon.ico +0 -0
- package/build/icon.iconset/icon_128x128.png +0 -0
- package/build/icon.iconset/icon_128x128@2x.png +0 -0
- package/build/icon.iconset/icon_16x16.png +0 -0
- package/build/icon.iconset/icon_16x16@2x.png +0 -0
- package/build/icon.iconset/icon_256x256.png +0 -0
- package/build/icon.iconset/icon_256x256@2x.png +0 -0
- package/build/icon.iconset/icon_32x32.png +0 -0
- package/build/icon.iconset/icon_32x32@2x.png +0 -0
- package/build/icon.iconset/icon_512x512.png +0 -0
- package/build/icon.iconset/icon_512x512@2x.png +0 -0
- package/build/icon.png +0 -0
- package/build/installer-header.bmp +0 -0
- package/build/installer-header.png +0 -0
- package/build/installer-sidebar.bmp +0 -0
- package/build/installer-sidebar.png +0 -0
- package/build/installer.nsh +45 -0
- package/build/overlay-processing.png +0 -0
- package/build/overlay-recording.png +0 -0
- package/build/toolbar-record.png +0 -0
- package/build/toolbar-screenshot.png +0 -0
- package/build/toolbar-settings.png +0 -0
- package/build/toolbar-stop.png +0 -0
- package/dist/main/index.mjs +12612 -0
- package/dist/preload/index.mjs +907 -0
- package/dist/renderer/assets/index-CCmUjl9K.js +19495 -0
- package/dist/renderer/assets/index-CUqz_Gs6.css +2270 -0
- package/dist/renderer/index.html +27 -0
- package/docs/AI_AGENT_QUICKSTART.md +42 -0
- package/docs/AI_PIPELINE_DESIGN.md +595 -0
- package/docs/API.md +514 -0
- package/docs/ARCHITECTURE.md +460 -0
- package/docs/CONFIGURATION.md +336 -0
- package/docs/DEVELOPMENT.md +508 -0
- package/docs/EXPORT_FORMATS.md +451 -0
- package/docs/GETTING_STARTED.md +236 -0
- package/docs/KEYBOARD_SHORTCUTS.md +334 -0
- package/docs/TROUBLESHOOTING.md +418 -0
- package/docs/landing/index.html +672 -0
- package/docs/landing/script.js +342 -0
- package/docs/landing/styles.css +1543 -0
- package/electron-builder.yml +140 -0
- package/electron.vite.config.ts +63 -0
- package/package.json +108 -0
- package/railway.json +12 -0
- package/scripts/build.mjs +51 -0
- package/scripts/generate-icons.mjs +314 -0
- package/scripts/generate-installer-images.cjs +253 -0
- package/scripts/generate-tray-icons.mjs +258 -0
- package/scripts/notarize.cjs +180 -0
- package/scripts/one-click-clean-test.sh +147 -0
- package/scripts/postinstall.mjs +36 -0
- package/scripts/setup-markupr.sh +55 -0
- package/setup +17 -0
- package/site/index.html +1835 -0
- package/site/package.json +11 -0
- package/site/railway.json +12 -0
- package/site/server.js +31 -0
- package/src/main/AutoUpdater.ts +392 -0
- package/src/main/CrashRecovery.ts +655 -0
- package/src/main/ErrorHandler.ts +703 -0
- package/src/main/HotkeyManager.ts +399 -0
- package/src/main/MenuManager.ts +529 -0
- package/src/main/PermissionManager.ts +420 -0
- package/src/main/SessionController.ts +1465 -0
- package/src/main/TrayManager.ts +540 -0
- package/src/main/ai/AIPipelineManager.ts +199 -0
- package/src/main/ai/ClaudeAnalyzer.ts +339 -0
- package/src/main/ai/ImageOptimizer.ts +176 -0
- package/src/main/ai/StructuredMarkdownBuilder.ts +379 -0
- package/src/main/ai/index.ts +16 -0
- package/src/main/ai/types.ts +258 -0
- package/src/main/analysis/ClarificationGenerator.ts +385 -0
- package/src/main/analysis/FeedbackAnalyzer.ts +531 -0
- package/src/main/analysis/index.ts +19 -0
- package/src/main/audio/AudioCapture.ts +978 -0
- package/src/main/audio/audioUtils.ts +100 -0
- package/src/main/audio/index.ts +20 -0
- package/src/main/capture/index.ts +1 -0
- package/src/main/index.ts +1693 -0
- package/src/main/ipc/captureHandlers.ts +272 -0
- package/src/main/ipc/index.ts +45 -0
- package/src/main/ipc/outputHandlers.ts +302 -0
- package/src/main/ipc/sessionHandlers.ts +56 -0
- package/src/main/ipc/settingsHandlers.ts +471 -0
- package/src/main/ipc/types.ts +56 -0
- package/src/main/ipc/windowHandlers.ts +277 -0
- package/src/main/output/ClipboardService.ts +369 -0
- package/src/main/output/ExportService.ts +539 -0
- package/src/main/output/FileManager.ts +416 -0
- package/src/main/output/MarkdownGenerator.ts +791 -0
- package/src/main/output/MarkdownPatcher.ts +299 -0
- package/src/main/output/index.ts +186 -0
- package/src/main/output/sessionAdapter.ts +207 -0
- package/src/main/output/templates/html-template.ts +553 -0
- package/src/main/pipeline/FrameExtractor.ts +330 -0
- package/src/main/pipeline/PostProcessor.ts +399 -0
- package/src/main/pipeline/TranscriptAnalyzer.ts +226 -0
- package/src/main/pipeline/index.ts +36 -0
- package/src/main/platform/WindowsTaskbar.ts +600 -0
- package/src/main/platform/index.ts +16 -0
- package/src/main/settings/SettingsManager.ts +730 -0
- package/src/main/settings/index.ts +19 -0
- package/src/main/transcription/ModelDownloadManager.ts +494 -0
- package/src/main/transcription/TierManager.ts +219 -0
- package/src/main/transcription/TranscriptionRecoveryService.ts +340 -0
- package/src/main/transcription/WhisperService.ts +748 -0
- package/src/main/transcription/index.ts +56 -0
- package/src/main/transcription/types.ts +135 -0
- package/src/main/windows/PopoverManager.ts +284 -0
- package/src/main/windows/TaskbarIntegration.ts +452 -0
- package/src/main/windows/index.ts +23 -0
- package/src/preload/index.ts +1047 -0
- package/src/renderer/App.tsx +515 -0
- package/src/renderer/AppWrapper.tsx +28 -0
- package/src/renderer/assets/logo-dark.svg +7 -0
- package/src/renderer/assets/logo.svg +7 -0
- package/src/renderer/audio/AudioCaptureRenderer.ts +454 -0
- package/src/renderer/capture/ScreenRecordingRenderer.ts +492 -0
- package/src/renderer/components/AnnotationOverlay.tsx +836 -0
- package/src/renderer/components/AudioWaveform.tsx +811 -0
- package/src/renderer/components/ClarificationQuestions.tsx +656 -0
- package/src/renderer/components/CountdownTimer.tsx +495 -0
- package/src/renderer/components/CrashRecoveryDialog.tsx +632 -0
- package/src/renderer/components/DonateButton.tsx +127 -0
- package/src/renderer/components/ErrorBoundary.tsx +308 -0
- package/src/renderer/components/ExportDialog.tsx +872 -0
- package/src/renderer/components/HotkeyHint.tsx +261 -0
- package/src/renderer/components/KeyboardShortcuts.tsx +787 -0
- package/src/renderer/components/ModelDownloadDialog.tsx +844 -0
- package/src/renderer/components/Onboarding.tsx +1830 -0
- package/src/renderer/components/ProcessingOverlay.tsx +157 -0
- package/src/renderer/components/RecordingOverlay.tsx +423 -0
- package/src/renderer/components/SessionHistory.tsx +1746 -0
- package/src/renderer/components/SessionReview.tsx +1321 -0
- package/src/renderer/components/SettingsPanel.tsx +217 -0
- package/src/renderer/components/Skeleton.tsx +347 -0
- package/src/renderer/components/StatusIndicator.tsx +86 -0
- package/src/renderer/components/ThemeProvider.tsx +429 -0
- package/src/renderer/components/Tooltip.tsx +370 -0
- package/src/renderer/components/TranscriptionPreview.tsx +183 -0
- package/src/renderer/components/TranscriptionTierSelector.tsx +640 -0
- package/src/renderer/components/UpdateNotification.tsx +377 -0
- package/src/renderer/components/WindowSelector.tsx +947 -0
- package/src/renderer/components/index.ts +99 -0
- package/src/renderer/components/primitives/ApiKeyInput.tsx +98 -0
- package/src/renderer/components/primitives/ColorPicker.tsx +65 -0
- package/src/renderer/components/primitives/DangerButton.tsx +45 -0
- package/src/renderer/components/primitives/DirectoryPicker.tsx +41 -0
- package/src/renderer/components/primitives/Dropdown.tsx +34 -0
- package/src/renderer/components/primitives/KeyRecorder.tsx +117 -0
- package/src/renderer/components/primitives/SettingsSection.tsx +32 -0
- package/src/renderer/components/primitives/Slider.tsx +43 -0
- package/src/renderer/components/primitives/Toggle.tsx +36 -0
- package/src/renderer/components/primitives/index.ts +10 -0
- package/src/renderer/components/settings/AdvancedTab.tsx +174 -0
- package/src/renderer/components/settings/AppearanceTab.tsx +77 -0
- package/src/renderer/components/settings/GeneralTab.tsx +40 -0
- package/src/renderer/components/settings/HotkeysTab.tsx +79 -0
- package/src/renderer/components/settings/RecordingTab.tsx +84 -0
- package/src/renderer/components/settings/index.ts +9 -0
- package/src/renderer/components/settings/settingsStyles.ts +673 -0
- package/src/renderer/components/settings/tabConfig.tsx +85 -0
- package/src/renderer/components/settings/useSettingsPanel.ts +447 -0
- package/src/renderer/contexts/ProcessingContext.tsx +227 -0
- package/src/renderer/contexts/RecordingContext.tsx +683 -0
- package/src/renderer/contexts/UIContext.tsx +326 -0
- package/src/renderer/contexts/index.ts +24 -0
- package/src/renderer/donateMessages.ts +69 -0
- package/src/renderer/hooks/index.ts +75 -0
- package/src/renderer/hooks/useAnimation.tsx +544 -0
- package/src/renderer/hooks/useTheme.ts +313 -0
- package/src/renderer/index.html +26 -0
- package/src/renderer/main.tsx +52 -0
- package/src/renderer/styles/animations.css +1093 -0
- package/src/renderer/styles/app-shell.css +662 -0
- package/src/renderer/styles/globals.css +515 -0
- package/src/renderer/styles/theme.ts +578 -0
- package/src/renderer/types/electron.d.ts +385 -0
- package/src/shared/hotkeys.ts +283 -0
- package/src/shared/types.ts +809 -0
- package/tests/clipboard.test.ts +228 -0
- package/tests/e2e/criticalPaths.test.ts +594 -0
- package/tests/feedbackAnalyzer.test.ts +303 -0
- package/tests/integration/sessionFlow.test.ts +583 -0
- package/tests/markdownGenerator.test.ts +418 -0
- package/tests/output.test.ts +96 -0
- package/tests/setup.ts +486 -0
- package/tests/unit/appIntegration.test.ts +676 -0
- package/tests/unit/appViewState.test.ts +281 -0
- package/tests/unit/audioIpcChannels.test.ts +17 -0
- package/tests/unit/exportService.test.ts +492 -0
- package/tests/unit/hotkeys.test.ts +92 -0
- package/tests/unit/navigationPreload.test.ts +94 -0
- package/tests/unit/onboardingFlow.test.ts +345 -0
- package/tests/unit/permissionManager.test.ts +175 -0
- package/tests/unit/permissionManagerExpanded.test.ts +296 -0
- package/tests/unit/screenRecordingRenderer.test.ts +368 -0
- package/tests/unit/sessionController.test.ts +515 -0
- package/tests/unit/tierManager.test.ts +61 -0
- package/tests/unit/tierManagerExpanded.test.ts +142 -0
- package/tests/unit/transcriptAnalyzer.test.ts +64 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +46 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WindowsTaskbar - Windows-specific taskbar integration for markupr
|
|
3
|
+
*
|
|
4
|
+
* Provides native Windows taskbar features:
|
|
5
|
+
* - Jump lists with recent sessions and quick actions
|
|
6
|
+
* - Taskbar progress bar during export/processing
|
|
7
|
+
* - Overlay icons for recording/processing states
|
|
8
|
+
* - Thumbnail toolbar buttons for quick actions
|
|
9
|
+
* - Frame flashing for completion notifications
|
|
10
|
+
* - Custom thumbnail clip regions
|
|
11
|
+
*
|
|
12
|
+
* All methods are no-ops on non-Windows platforms.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { BrowserWindow, app, nativeImage } from 'electron';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
|
|
18
|
+
export interface RecentSession {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
path: string;
|
|
22
|
+
date: Date;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TaskbarActionCallback {
|
|
26
|
+
onRecord: () => void;
|
|
27
|
+
onStop: () => void;
|
|
28
|
+
onScreenshot: () => void;
|
|
29
|
+
onSettings: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type OverlayState = 'recording' | 'processing' | 'none';
|
|
33
|
+
type ProgressMode = 'none' | 'normal' | 'indeterminate' | 'error' | 'paused';
|
|
34
|
+
|
|
35
|
+
// Local interface for ThumbarButton since Electron types may vary
|
|
36
|
+
interface ThumbarButton {
|
|
37
|
+
tooltip: string;
|
|
38
|
+
icon: Electron.NativeImage;
|
|
39
|
+
flags?: ('enabled' | 'disabled' | 'dismissonclick' | 'nobackground' | 'hidden' | 'noninteractive')[];
|
|
40
|
+
click: () => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class WindowsTaskbar {
|
|
44
|
+
private mainWindow: BrowserWindow;
|
|
45
|
+
private isWindows: boolean;
|
|
46
|
+
private recentSessions: RecentSession[] = [];
|
|
47
|
+
private currentOverlayState: OverlayState = 'none';
|
|
48
|
+
private isRecording = false;
|
|
49
|
+
private actionCallbacks: TaskbarActionCallback | null = null;
|
|
50
|
+
private assetsPath: string;
|
|
51
|
+
|
|
52
|
+
constructor(mainWindow: BrowserWindow) {
|
|
53
|
+
this.mainWindow = mainWindow;
|
|
54
|
+
this.isWindows = process.platform === 'win32';
|
|
55
|
+
|
|
56
|
+
// Assets are in build/ directory during development, resources/ in production
|
|
57
|
+
this.assetsPath = app.isPackaged
|
|
58
|
+
? join(process.resourcesPath, 'build')
|
|
59
|
+
: join(app.getAppPath(), 'build');
|
|
60
|
+
|
|
61
|
+
if (!this.isWindows) {
|
|
62
|
+
console.log('[WindowsTaskbar] Not on Windows, taskbar features disabled');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Set action callbacks for thumbnail toolbar buttons
|
|
68
|
+
*/
|
|
69
|
+
setActionCallbacks(callbacks: TaskbarActionCallback): void {
|
|
70
|
+
this.actionCallbacks = callbacks;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Initialize the taskbar with default state
|
|
75
|
+
*/
|
|
76
|
+
initialize(): void {
|
|
77
|
+
if (!this.isWindows) return;
|
|
78
|
+
|
|
79
|
+
console.log('[WindowsTaskbar] Initializing Windows taskbar integration');
|
|
80
|
+
|
|
81
|
+
this.setupJumpList();
|
|
82
|
+
this.setupThumbnailToolbar();
|
|
83
|
+
this.setOverlayIcon('none');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Set up Windows jump list with recent sessions and tasks
|
|
88
|
+
*
|
|
89
|
+
* Jump list structure:
|
|
90
|
+
* - Recent Sessions (user tasks)
|
|
91
|
+
* - Tasks (standard actions)
|
|
92
|
+
*/
|
|
93
|
+
setupJumpList(): void {
|
|
94
|
+
if (!this.isWindows) return;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const jumpListItems: Electron.JumpListCategory[] = [];
|
|
98
|
+
|
|
99
|
+
// Recent Sessions category
|
|
100
|
+
if (this.recentSessions.length > 0) {
|
|
101
|
+
const recentItems: Electron.JumpListItem[] = this.recentSessions
|
|
102
|
+
.slice(0, 10) // Max 10 recent items
|
|
103
|
+
.map((session) => ({
|
|
104
|
+
type: 'task' as const,
|
|
105
|
+
title: session.name,
|
|
106
|
+
description: `Opened ${session.date.toLocaleDateString()}`,
|
|
107
|
+
program: process.execPath,
|
|
108
|
+
args: `--open-session "${session.path}"`,
|
|
109
|
+
iconPath: process.execPath,
|
|
110
|
+
iconIndex: 0,
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
jumpListItems.push({
|
|
114
|
+
type: 'custom',
|
|
115
|
+
name: 'Recent Sessions',
|
|
116
|
+
items: recentItems,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Tasks category - quick actions
|
|
121
|
+
const tasks: Electron.JumpListItem[] = [
|
|
122
|
+
{
|
|
123
|
+
type: 'task',
|
|
124
|
+
title: 'New Recording',
|
|
125
|
+
description: 'Start a new feedback recording session',
|
|
126
|
+
program: process.execPath,
|
|
127
|
+
args: '--new-recording',
|
|
128
|
+
iconPath: process.execPath,
|
|
129
|
+
iconIndex: 0,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: 'task',
|
|
133
|
+
title: 'Open Settings',
|
|
134
|
+
description: 'Configure markupr settings',
|
|
135
|
+
program: process.execPath,
|
|
136
|
+
args: '--settings',
|
|
137
|
+
iconPath: process.execPath,
|
|
138
|
+
iconIndex: 0,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: 'task',
|
|
142
|
+
title: 'Check for Updates',
|
|
143
|
+
description: 'Check for application updates',
|
|
144
|
+
program: process.execPath,
|
|
145
|
+
args: '--check-updates',
|
|
146
|
+
iconPath: process.execPath,
|
|
147
|
+
iconIndex: 0,
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
jumpListItems.push({
|
|
152
|
+
type: 'tasks',
|
|
153
|
+
items: tasks,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
app.setJumpList(jumpListItems);
|
|
157
|
+
console.log('[WindowsTaskbar] Jump list configured');
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('[WindowsTaskbar] Failed to set jump list:', error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Update the list of recent sessions in the jump list
|
|
165
|
+
*/
|
|
166
|
+
updateRecentSessions(sessions: RecentSession[]): void {
|
|
167
|
+
this.recentSessions = sessions;
|
|
168
|
+
this.setupJumpList();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Set taskbar progress bar
|
|
173
|
+
*
|
|
174
|
+
* @param progress - Progress value:
|
|
175
|
+
* - 0 to 1: Normal progress percentage
|
|
176
|
+
* - -1: Indeterminate (spinning) progress
|
|
177
|
+
* - 2: Clear/remove progress bar
|
|
178
|
+
*/
|
|
179
|
+
setProgress(progress: number): void {
|
|
180
|
+
if (!this.isWindows) return;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
if (progress === 2 || progress < 0 && progress !== -1) {
|
|
184
|
+
// Clear progress bar
|
|
185
|
+
this.mainWindow.setProgressBar(-1);
|
|
186
|
+
} else if (progress === -1) {
|
|
187
|
+
// Indeterminate progress
|
|
188
|
+
this.mainWindow.setProgressBar(2, { mode: 'indeterminate' });
|
|
189
|
+
} else {
|
|
190
|
+
// Normal progress (0-1)
|
|
191
|
+
const clampedProgress = Math.max(0, Math.min(1, progress));
|
|
192
|
+
this.mainWindow.setProgressBar(clampedProgress, { mode: 'normal' });
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('[WindowsTaskbar] Failed to set progress:', error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Set progress bar with specific mode
|
|
201
|
+
*/
|
|
202
|
+
setProgressWithMode(progress: number, mode: ProgressMode): void {
|
|
203
|
+
if (!this.isWindows) return;
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
if (mode === 'none') {
|
|
207
|
+
this.mainWindow.setProgressBar(-1);
|
|
208
|
+
} else {
|
|
209
|
+
const clampedProgress = Math.max(0, Math.min(1, progress));
|
|
210
|
+
this.mainWindow.setProgressBar(clampedProgress, { mode });
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error('[WindowsTaskbar] Failed to set progress with mode:', error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Clear the progress bar
|
|
219
|
+
*/
|
|
220
|
+
clearProgress(): void {
|
|
221
|
+
this.setProgress(2);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Set overlay icon on the taskbar icon
|
|
226
|
+
* Used to indicate recording/processing state
|
|
227
|
+
*/
|
|
228
|
+
setOverlayIcon(state: OverlayState): void {
|
|
229
|
+
if (!this.isWindows) return;
|
|
230
|
+
|
|
231
|
+
this.currentOverlayState = state;
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
if (state === 'none') {
|
|
235
|
+
this.mainWindow.setOverlayIcon(null, '');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const iconName = state === 'recording'
|
|
240
|
+
? 'overlay-recording.png'
|
|
241
|
+
: 'overlay-processing.png';
|
|
242
|
+
|
|
243
|
+
const iconPath = join(this.assetsPath, iconName);
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const icon = nativeImage.createFromPath(iconPath);
|
|
247
|
+
|
|
248
|
+
if (icon.isEmpty()) {
|
|
249
|
+
// Fallback: create a simple colored icon programmatically
|
|
250
|
+
const fallbackIcon = this.createFallbackOverlayIcon(state);
|
|
251
|
+
const description = state === 'recording' ? 'Recording' : 'Processing';
|
|
252
|
+
this.mainWindow.setOverlayIcon(fallbackIcon, description);
|
|
253
|
+
console.log(`[WindowsTaskbar] Using fallback overlay icon for ${state}`);
|
|
254
|
+
} else {
|
|
255
|
+
const description = state === 'recording' ? 'Recording' : 'Processing';
|
|
256
|
+
this.mainWindow.setOverlayIcon(icon, description);
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
// If icon file doesn't exist, create fallback
|
|
260
|
+
const fallbackIcon = this.createFallbackOverlayIcon(state);
|
|
261
|
+
const description = state === 'recording' ? 'Recording' : 'Processing';
|
|
262
|
+
this.mainWindow.setOverlayIcon(fallbackIcon, description);
|
|
263
|
+
console.log(`[WindowsTaskbar] Icon not found, using fallback for ${state}`);
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error('[WindowsTaskbar] Failed to set overlay icon:', error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Create a fallback overlay icon programmatically
|
|
272
|
+
*/
|
|
273
|
+
private createFallbackOverlayIcon(state: OverlayState): Electron.NativeImage {
|
|
274
|
+
// Create a 16x16 icon using data URL
|
|
275
|
+
const size = 16;
|
|
276
|
+
const color = state === 'recording' ? '#FF4444' : '#4488FF';
|
|
277
|
+
|
|
278
|
+
// Simple SVG circle
|
|
279
|
+
const svg = `
|
|
280
|
+
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
|
|
281
|
+
<circle cx="${size/2}" cy="${size/2}" r="${size/2 - 1}" fill="${color}" />
|
|
282
|
+
</svg>
|
|
283
|
+
`;
|
|
284
|
+
|
|
285
|
+
const base64 = Buffer.from(svg).toString('base64');
|
|
286
|
+
return nativeImage.createFromDataURL(`data:image/svg+xml;base64,${base64}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Set up thumbnail toolbar buttons
|
|
291
|
+
* These appear when hovering over the taskbar icon
|
|
292
|
+
*/
|
|
293
|
+
setupThumbnailToolbar(): void {
|
|
294
|
+
if (!this.isWindows) return;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const buttons = this.createThumbnailButtons();
|
|
298
|
+
this.mainWindow.setThumbarButtons(buttons);
|
|
299
|
+
console.log('[WindowsTaskbar] Thumbnail toolbar configured');
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error('[WindowsTaskbar] Failed to set thumbnail toolbar:', error);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Update thumbnail toolbar based on recording state
|
|
307
|
+
*/
|
|
308
|
+
updateThumbnailToolbar(isRecording: boolean): void {
|
|
309
|
+
if (!this.isWindows) return;
|
|
310
|
+
|
|
311
|
+
this.isRecording = isRecording;
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const buttons = this.createThumbnailButtons();
|
|
315
|
+
this.mainWindow.setThumbarButtons(buttons);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('[WindowsTaskbar] Failed to update thumbnail toolbar:', error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Create thumbnail toolbar buttons based on current state
|
|
323
|
+
*/
|
|
324
|
+
private createThumbnailButtons(): ThumbarButton[] {
|
|
325
|
+
const buttons: ThumbarButton[] = [];
|
|
326
|
+
|
|
327
|
+
// Record/Stop button
|
|
328
|
+
if (this.isRecording) {
|
|
329
|
+
buttons.push({
|
|
330
|
+
tooltip: 'Stop Recording',
|
|
331
|
+
icon: this.loadToolbarIcon('toolbar-stop.png'),
|
|
332
|
+
click: () => {
|
|
333
|
+
console.log('[WindowsTaskbar] Stop button clicked');
|
|
334
|
+
this.actionCallbacks?.onStop();
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
} else {
|
|
338
|
+
buttons.push({
|
|
339
|
+
tooltip: 'Start Recording',
|
|
340
|
+
icon: this.loadToolbarIcon('toolbar-record.png'),
|
|
341
|
+
click: () => {
|
|
342
|
+
console.log('[WindowsTaskbar] Record button clicked');
|
|
343
|
+
this.actionCallbacks?.onRecord();
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Screenshot button (only enabled during recording)
|
|
349
|
+
buttons.push({
|
|
350
|
+
tooltip: 'Take Screenshot',
|
|
351
|
+
icon: this.loadToolbarIcon('toolbar-screenshot.png'),
|
|
352
|
+
flags: this.isRecording ? [] : ['disabled'],
|
|
353
|
+
click: () => {
|
|
354
|
+
console.log('[WindowsTaskbar] Screenshot button clicked');
|
|
355
|
+
this.actionCallbacks?.onScreenshot();
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Settings button
|
|
360
|
+
buttons.push({
|
|
361
|
+
tooltip: 'Settings',
|
|
362
|
+
icon: this.loadToolbarIcon('toolbar-settings.png'),
|
|
363
|
+
click: () => {
|
|
364
|
+
console.log('[WindowsTaskbar] Settings button clicked');
|
|
365
|
+
this.actionCallbacks?.onSettings();
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
return buttons;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Load a toolbar icon, with fallback to programmatic icon
|
|
374
|
+
*/
|
|
375
|
+
private loadToolbarIcon(iconName: string): Electron.NativeImage {
|
|
376
|
+
const iconPath = join(this.assetsPath, iconName);
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const icon = nativeImage.createFromPath(iconPath);
|
|
380
|
+
|
|
381
|
+
if (!icon.isEmpty()) {
|
|
382
|
+
return icon;
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
// Fall through to create fallback
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Create fallback icon
|
|
389
|
+
return this.createFallbackToolbarIcon(iconName);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Create a fallback toolbar icon programmatically
|
|
394
|
+
*/
|
|
395
|
+
private createFallbackToolbarIcon(iconName: string): Electron.NativeImage {
|
|
396
|
+
const size = 16;
|
|
397
|
+
let svgContent: string;
|
|
398
|
+
|
|
399
|
+
if (iconName.includes('record')) {
|
|
400
|
+
// Red circle for record
|
|
401
|
+
svgContent = `
|
|
402
|
+
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
|
|
403
|
+
<circle cx="${size/2}" cy="${size/2}" r="${size/2 - 2}" fill="#FF4444" />
|
|
404
|
+
</svg>
|
|
405
|
+
`;
|
|
406
|
+
} else if (iconName.includes('stop')) {
|
|
407
|
+
// White square for stop
|
|
408
|
+
svgContent = `
|
|
409
|
+
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
|
|
410
|
+
<rect x="3" y="3" width="${size - 6}" height="${size - 6}" fill="#FFFFFF" />
|
|
411
|
+
</svg>
|
|
412
|
+
`;
|
|
413
|
+
} else if (iconName.includes('screenshot')) {
|
|
414
|
+
// Camera-like icon for screenshot
|
|
415
|
+
svgContent = `
|
|
416
|
+
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
|
|
417
|
+
<rect x="2" y="4" width="12" height="9" rx="1" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
|
418
|
+
<circle cx="8" cy="8" r="2" fill="#FFFFFF"/>
|
|
419
|
+
</svg>
|
|
420
|
+
`;
|
|
421
|
+
} else if (iconName.includes('settings')) {
|
|
422
|
+
// Gear-like icon for settings
|
|
423
|
+
svgContent = `
|
|
424
|
+
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
|
|
425
|
+
<circle cx="${size/2}" cy="${size/2}" r="3" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
|
426
|
+
<circle cx="${size/2}" cy="${size/2}" r="6" fill="none" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="2 2"/>
|
|
427
|
+
</svg>
|
|
428
|
+
`;
|
|
429
|
+
} else {
|
|
430
|
+
// Default: simple white circle
|
|
431
|
+
svgContent = `
|
|
432
|
+
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
|
|
433
|
+
<circle cx="${size/2}" cy="${size/2}" r="${size/2 - 2}" fill="#FFFFFF" />
|
|
434
|
+
</svg>
|
|
435
|
+
`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const base64 = Buffer.from(svgContent).toString('base64');
|
|
439
|
+
return nativeImage.createFromDataURL(`data:image/svg+xml;base64,${base64}`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Flash the taskbar button to get user attention
|
|
444
|
+
*
|
|
445
|
+
* @param count - Number of times to flash (0 for continuous until focused)
|
|
446
|
+
*/
|
|
447
|
+
flashFrame(count?: number): void {
|
|
448
|
+
if (!this.isWindows) return;
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
if (count === 0 || count === undefined) {
|
|
452
|
+
// Flash until the window is focused
|
|
453
|
+
this.mainWindow.flashFrame(true);
|
|
454
|
+
|
|
455
|
+
// Stop flashing when window gains focus
|
|
456
|
+
const stopFlashing = (): void => {
|
|
457
|
+
this.mainWindow.flashFrame(false);
|
|
458
|
+
this.mainWindow.removeListener('focus', stopFlashing);
|
|
459
|
+
};
|
|
460
|
+
this.mainWindow.once('focus', stopFlashing);
|
|
461
|
+
} else {
|
|
462
|
+
// Flash a specific number of times
|
|
463
|
+
let flashCount = 0;
|
|
464
|
+
const interval = setInterval(() => {
|
|
465
|
+
if (flashCount >= count * 2) {
|
|
466
|
+
clearInterval(interval);
|
|
467
|
+
this.mainWindow.flashFrame(false);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
this.mainWindow.flashFrame(flashCount % 2 === 0);
|
|
472
|
+
flashCount++;
|
|
473
|
+
}, 500);
|
|
474
|
+
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
console.error('[WindowsTaskbar] Failed to flash frame:', error);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Stop flashing the taskbar button
|
|
482
|
+
*/
|
|
483
|
+
stopFlashing(): void {
|
|
484
|
+
if (!this.isWindows) return;
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
this.mainWindow.flashFrame(false);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error('[WindowsTaskbar] Failed to stop flashing:', error);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Set a custom thumbnail clip region
|
|
495
|
+
* Used to show a specific part of the window in the thumbnail preview
|
|
496
|
+
*
|
|
497
|
+
* @param region - Rectangle defining the clip region, or undefined to reset
|
|
498
|
+
*/
|
|
499
|
+
setThumbnailClip(region?: Electron.Rectangle): void {
|
|
500
|
+
if (!this.isWindows) return;
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
if (region) {
|
|
504
|
+
this.mainWindow.setThumbnailClip(region);
|
|
505
|
+
} else {
|
|
506
|
+
// Reset to full window
|
|
507
|
+
this.mainWindow.setThumbnailClip({ x: 0, y: 0, width: 0, height: 0 });
|
|
508
|
+
}
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error('[WindowsTaskbar] Failed to set thumbnail clip:', error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Update the taskbar state based on session state.
|
|
516
|
+
* Supports the bulletproof 7-state machine.
|
|
517
|
+
*/
|
|
518
|
+
updateSessionState(
|
|
519
|
+
state: 'idle' | 'starting' | 'recording' | 'stopping' | 'processing' | 'complete' | 'error'
|
|
520
|
+
): void {
|
|
521
|
+
switch (state) {
|
|
522
|
+
case 'idle':
|
|
523
|
+
this.setOverlayIcon('none');
|
|
524
|
+
this.clearProgress();
|
|
525
|
+
this.updateThumbnailToolbar(false);
|
|
526
|
+
break;
|
|
527
|
+
case 'starting':
|
|
528
|
+
this.setOverlayIcon('processing');
|
|
529
|
+
this.setProgress(-1); // Indeterminate while starting
|
|
530
|
+
this.updateThumbnailToolbar(false);
|
|
531
|
+
break;
|
|
532
|
+
case 'recording':
|
|
533
|
+
this.setOverlayIcon('recording');
|
|
534
|
+
this.clearProgress();
|
|
535
|
+
this.updateThumbnailToolbar(true);
|
|
536
|
+
break;
|
|
537
|
+
case 'stopping':
|
|
538
|
+
this.setOverlayIcon('processing');
|
|
539
|
+
this.setProgress(-1); // Indeterminate while stopping
|
|
540
|
+
this.updateThumbnailToolbar(false);
|
|
541
|
+
break;
|
|
542
|
+
case 'processing':
|
|
543
|
+
this.setOverlayIcon('processing');
|
|
544
|
+
this.setProgress(-1); // Indeterminate
|
|
545
|
+
this.updateThumbnailToolbar(false);
|
|
546
|
+
break;
|
|
547
|
+
case 'complete':
|
|
548
|
+
this.setOverlayIcon('none');
|
|
549
|
+
this.clearProgress();
|
|
550
|
+
this.updateThumbnailToolbar(false);
|
|
551
|
+
this.flashFrame(3); // Flash 3 times on completion
|
|
552
|
+
break;
|
|
553
|
+
case 'error':
|
|
554
|
+
this.setOverlayIcon('none');
|
|
555
|
+
this.clearProgress();
|
|
556
|
+
this.updateThumbnailToolbar(false);
|
|
557
|
+
this.flashFrame(2); // Flash 2 times on error
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Clean up taskbar resources
|
|
564
|
+
*/
|
|
565
|
+
destroy(): void {
|
|
566
|
+
if (!this.isWindows) return;
|
|
567
|
+
|
|
568
|
+
try {
|
|
569
|
+
// Clear overlay icon
|
|
570
|
+
this.mainWindow.setOverlayIcon(null, '');
|
|
571
|
+
|
|
572
|
+
// Clear progress
|
|
573
|
+
this.mainWindow.setProgressBar(-1);
|
|
574
|
+
|
|
575
|
+
// Clear thumbnail toolbar
|
|
576
|
+
this.mainWindow.setThumbarButtons([]);
|
|
577
|
+
|
|
578
|
+
// Stop any flashing
|
|
579
|
+
this.mainWindow.flashFrame(false);
|
|
580
|
+
|
|
581
|
+
console.log('[WindowsTaskbar] Cleaned up');
|
|
582
|
+
} catch (error) {
|
|
583
|
+
console.error('[WindowsTaskbar] Error during cleanup:', error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Export singleton factory
|
|
589
|
+
let windowsTaskbarInstance: WindowsTaskbar | null = null;
|
|
590
|
+
|
|
591
|
+
export function createWindowsTaskbar(mainWindow: BrowserWindow): WindowsTaskbar {
|
|
592
|
+
windowsTaskbarInstance = new WindowsTaskbar(mainWindow);
|
|
593
|
+
return windowsTaskbarInstance;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export function getWindowsTaskbar(): WindowsTaskbar | null {
|
|
597
|
+
return windowsTaskbarInstance;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export default WindowsTaskbar;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-specific integrations for markupr
|
|
3
|
+
*
|
|
4
|
+
* Provides native OS features:
|
|
5
|
+
* - Windows: Taskbar integration (jump lists, progress, overlay icons, thumbnail toolbar)
|
|
6
|
+
* - macOS: Dock integration (handled by TrayManager and MenuManager)
|
|
7
|
+
* - Linux: Unity launcher (future)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
WindowsTaskbar,
|
|
12
|
+
createWindowsTaskbar,
|
|
13
|
+
getWindowsTaskbar,
|
|
14
|
+
type RecentSession,
|
|
15
|
+
type TaskbarActionCallback,
|
|
16
|
+
} from './WindowsTaskbar';
|