mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 07:16:02 +00:00
Split all source files exceeding 500 lines into smaller, focused modules:
- electron/main.ts → mainBootstrapHelpers, mainRuntimeState, mainUpdateIntegration, mainWindowControls
- electron/windows.ts → editorWindows, hudWindows, windowShared
- electron/updater.ts → updaterDialogs, updaterEventHandlers, updaterShared
- electron/preload.ts → preloadExtensionsBridge, preloadUpdateBridge
- electron/ipc/register/recording.ts → recording/ directory with focused handlers
- src/components/video-editor/VideoEditor.tsx → EditorContent, EditorHeader, EditorSidebar, EditorToolbar, hooks/
- src/components/video-editor/SettingsPanel.tsx → settings/ directory with per-section components
- src/components/video-editor/AnnotationSettingsPanel.tsx → tab components
- src/components/video-editor/ExtensionManager.tsx → extension-manager/ directory
- src/components/video-editor/VideoPlayback.tsx → videoPlaybackComponent/ directory
- src/components/video-editor/projectPersistence.ts → projectPersistence{Normalization,Paths,Regions,Shared}.ts
- src/components/video-editor/timeline/TimelineEditor.tsx → TimelineEditor/ directory
- src/components/video-editor/types.ts → focused type modules
- src/components/launch/LaunchWindow.tsx → LaunchWindow/ directory
- src/hooks/useScreenRecorder.ts → useScreenRecorder/ directory
- src/lib/exporter/audioEncoder.ts → audioEncoder/ directory
- src/lib/exporter/frameRenderer.ts → frameRenderer modules
- src/lib/exporter/modernFrameRenderer.ts → filters and lifecycle modules
- src/lib/exporter/modernVideoExporter.ts → modernVideoExporter/ directory
- src/lib/exporter/videoExporter.ts → video-exporter/ directory
- src/lib/exporter/streamingDecoder.ts → streamingDecoderHelpers.ts
- src/lib/extensions/extensionHost.ts → extensionHost{ApiFactory,QueryApi,RegistrationApi,Shared,State}.ts
- src/lib/extensions/types.ts → focused type modules
- electron/native/ScreenCaptureKitRecorder.swift → ScreenCaptureKitRecorder/ directory
- scripts/benchmark-export-queues.mjs → benchmark-export-queues/ directory
- .github/workflows/release.yml → extracted merge-macos-metadata composite action
- scripts/build-native-helpers.mjs → updated for multi-file Swift compilation
Also incorporates upstream default export quality change (good → source).
408 lines
12 KiB
TypeScript
408 lines
12 KiB
TypeScript
import { autoUpdater } from "electron-updater";
|
|
import {
|
|
AUTO_UPDATES_DISABLED,
|
|
DEV_UPDATE_PREVIEW_PROGRESS_INCREMENT,
|
|
DEV_UPDATE_PREVIEW_PROGRESS_STEP_MS,
|
|
DEV_UPDATE_PREVIEW_VERSION,
|
|
DISMISSED_READY_REMINDER_DELAY_MS,
|
|
UPDATE_REMINDER_DELAY_MS,
|
|
UPDATER_LOG_PATH,
|
|
canUseAutoUpdates,
|
|
clearDeferredReminderTimer,
|
|
clearDevPreviewProgressTimer,
|
|
clearVisibleUpdateToast,
|
|
configureUpdateFeed,
|
|
createAutoCheckErrorToastPayload,
|
|
createDownloadedUpdateToastPayload,
|
|
createDownloadingUpdateToastPayload,
|
|
createUpdateErrorToastPayload,
|
|
emitUpdateToastState,
|
|
getReminderPayload,
|
|
isAutoUpdateFeatureEnabled,
|
|
setUpdateStatusSummary,
|
|
shouldSurfaceAutomaticCheckErrors,
|
|
showMessageBox,
|
|
type GetMainWindow,
|
|
type UpdateToastSender,
|
|
updaterState,
|
|
writeUpdaterLog,
|
|
} from "./updaterShared";
|
|
import {
|
|
showAvailableUpdateDialog,
|
|
showDownloadedUpdateDialog,
|
|
showNoUpdatesDialog,
|
|
showUpdateErrorDialog,
|
|
} from "./updaterDialogs";
|
|
import { registerAutoUpdaterEventHandlers } from "./updaterEventHandlers";
|
|
|
|
export { UPDATE_REMINDER_DELAY_MS } from "./updaterShared";
|
|
export type {
|
|
UpdateStatusKind,
|
|
UpdateStatusSummary,
|
|
UpdateToastPayload,
|
|
UpdateToastPhase,
|
|
} from "./updaterShared";
|
|
export { isAutoUpdateFeatureEnabled };
|
|
|
|
function resetDevPreviewState(sendToRenderer?: UpdateToastSender) {
|
|
clearDevPreviewProgressTimer();
|
|
updaterState.availableVersion = null;
|
|
updaterState.pendingDownloadedVersion = null;
|
|
updaterState.downloadInProgress = false;
|
|
updaterState.downloadToastDismissed = false;
|
|
updaterState.skippedVersion = null;
|
|
clearVisibleUpdateToast(sendToRenderer);
|
|
}
|
|
|
|
function simulateDevPreviewDownload(sendToRenderer?: UpdateToastSender) {
|
|
updaterState.availableVersion = DEV_UPDATE_PREVIEW_VERSION;
|
|
updaterState.pendingDownloadedVersion = null;
|
|
updaterState.downloadInProgress = true;
|
|
updaterState.downloadToastDismissed = false;
|
|
clearDeferredReminderTimer();
|
|
clearDevPreviewProgressTimer();
|
|
|
|
let progressPercent = 0;
|
|
emitUpdateToastState(sendToRenderer, {
|
|
...createDownloadingUpdateToastPayload(DEV_UPDATE_PREVIEW_VERSION, progressPercent),
|
|
isPreview: true,
|
|
});
|
|
|
|
updaterState.devPreviewProgressTimer = setInterval(() => {
|
|
progressPercent = Math.min(100, progressPercent + DEV_UPDATE_PREVIEW_PROGRESS_INCREMENT);
|
|
|
|
if (progressPercent >= 100) {
|
|
clearDevPreviewProgressTimer();
|
|
updaterState.downloadInProgress = false;
|
|
updaterState.pendingDownloadedVersion = DEV_UPDATE_PREVIEW_VERSION;
|
|
emitUpdateToastState(sendToRenderer, {
|
|
...createDownloadedUpdateToastPayload(DEV_UPDATE_PREVIEW_VERSION),
|
|
isPreview: true,
|
|
detail: "Development preview: the update is ready to install. No real update will be installed.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (updaterState.downloadToastDismissed) {
|
|
return;
|
|
}
|
|
|
|
emitUpdateToastState(sendToRenderer, {
|
|
...createDownloadingUpdateToastPayload(DEV_UPDATE_PREVIEW_VERSION, progressPercent),
|
|
isPreview: true,
|
|
});
|
|
}, DEV_UPDATE_PREVIEW_PROGRESS_STEP_MS);
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
export function getCurrentUpdateToastPayload() {
|
|
return updaterState.currentToastPayload;
|
|
}
|
|
|
|
export function getUpdaterLogPath() {
|
|
return UPDATER_LOG_PATH;
|
|
}
|
|
|
|
export function getUpdateStatusSummary() {
|
|
return updaterState.updateStatusSummary;
|
|
}
|
|
|
|
export function dismissUpdateToast(
|
|
getMainWindow: GetMainWindow,
|
|
sendToRenderer?: UpdateToastSender,
|
|
) {
|
|
if (updaterState.currentToastPayload?.isPreview) {
|
|
resetDevPreviewState(sendToRenderer);
|
|
return { success: true };
|
|
}
|
|
|
|
if (updaterState.downloadInProgress) {
|
|
updaterState.downloadToastDismissed = true;
|
|
clearVisibleUpdateToast(sendToRenderer);
|
|
return { success: true };
|
|
}
|
|
|
|
if (updaterState.currentToastPayload?.phase === "ready") {
|
|
return deferUpdateReminder(
|
|
getMainWindow,
|
|
sendToRenderer,
|
|
DISMISSED_READY_REMINDER_DELAY_MS,
|
|
);
|
|
}
|
|
|
|
if (
|
|
updaterState.currentToastPayload?.phase === "available" ||
|
|
updaterState.currentToastPayload?.phase === "error"
|
|
) {
|
|
return deferUpdateReminder(getMainWindow, sendToRenderer, UPDATE_REMINDER_DELAY_MS);
|
|
}
|
|
|
|
clearVisibleUpdateToast(sendToRenderer);
|
|
return { success: true };
|
|
}
|
|
|
|
export function installDownloadedUpdateNow(sendToRenderer?: UpdateToastSender) {
|
|
if (updaterState.currentToastPayload?.isPreview) {
|
|
resetDevPreviewState(sendToRenderer);
|
|
return;
|
|
}
|
|
|
|
clearDeferredReminderTimer();
|
|
updaterState.downloadToastDismissed = false;
|
|
clearVisibleUpdateToast(sendToRenderer);
|
|
setUpdateStatusSummary({
|
|
status: "ready",
|
|
availableVersion: updaterState.pendingDownloadedVersion,
|
|
});
|
|
writeUpdaterLog("Installing downloaded update.");
|
|
autoUpdater.quitAndInstall();
|
|
}
|
|
|
|
export async function downloadAvailableUpdate(sendToRenderer?: UpdateToastSender) {
|
|
if (updaterState.currentToastPayload?.isPreview) {
|
|
return simulateDevPreviewDownload(sendToRenderer);
|
|
}
|
|
|
|
if (!updaterState.availableVersion) {
|
|
return { success: false, message: "No update is ready to download." };
|
|
}
|
|
|
|
if (updaterState.pendingDownloadedVersion === updaterState.availableVersion) {
|
|
return { success: false, message: "This update has already been downloaded." };
|
|
}
|
|
|
|
if (updaterState.downloadInProgress) {
|
|
return { success: false, message: "This update is already downloading." };
|
|
}
|
|
|
|
clearDeferredReminderTimer();
|
|
updaterState.downloadInProgress = true;
|
|
updaterState.downloadToastDismissed = false;
|
|
setUpdateStatusSummary({
|
|
status: "downloading",
|
|
availableVersion: updaterState.availableVersion,
|
|
detail: `Downloading Recordly ${updaterState.availableVersion}`,
|
|
});
|
|
emitUpdateToastState(
|
|
sendToRenderer,
|
|
createDownloadingUpdateToastPayload(updaterState.availableVersion, 0),
|
|
);
|
|
writeUpdaterLog(`Starting update download for ${updaterState.availableVersion}.`);
|
|
|
|
try {
|
|
await autoUpdater.downloadUpdate();
|
|
writeUpdaterLog(`Update download requested for ${updaterState.availableVersion}.`);
|
|
return { success: true };
|
|
} catch (error) {
|
|
updaterState.downloadInProgress = false;
|
|
setUpdateStatusSummary({
|
|
status: "error",
|
|
availableVersion: updaterState.availableVersion,
|
|
detail: String(error),
|
|
});
|
|
writeUpdaterLog(`Update download failed for ${updaterState.availableVersion}.`, error);
|
|
emitUpdateToastState(
|
|
sendToRenderer,
|
|
createUpdateErrorToastPayload(updaterState.availableVersion ?? "unknown", error),
|
|
);
|
|
return { success: false, message: String(error) };
|
|
}
|
|
}
|
|
|
|
export function deferUpdateReminder(
|
|
getMainWindow: GetMainWindow,
|
|
sendToRenderer?: UpdateToastSender,
|
|
delayMs = UPDATE_REMINDER_DELAY_MS,
|
|
) {
|
|
const payload = getReminderPayload();
|
|
if (!payload) {
|
|
return { success: false, message: "No update reminder is ready yet." };
|
|
}
|
|
|
|
clearDeferredReminderTimer();
|
|
clearVisibleUpdateToast(sendToRenderer);
|
|
updaterState.deferredReminderTimer = setTimeout(() => {
|
|
const nextPayload = getReminderPayload();
|
|
if (!nextPayload) {
|
|
return;
|
|
}
|
|
|
|
if (sendToRenderer && emitUpdateToastState(sendToRenderer, nextPayload)) {
|
|
return;
|
|
}
|
|
|
|
if (nextPayload.phase === "ready") {
|
|
void showDownloadedUpdateDialog(
|
|
getMainWindow,
|
|
nextPayload.version,
|
|
{
|
|
downloadAvailableUpdate,
|
|
deferUpdateReminder,
|
|
skipAvailableUpdateVersion,
|
|
installDownloadedUpdateNow,
|
|
},
|
|
);
|
|
return;
|
|
}
|
|
|
|
void showAvailableUpdateDialog(
|
|
getMainWindow,
|
|
nextPayload.version,
|
|
sendToRenderer,
|
|
{
|
|
downloadAvailableUpdate,
|
|
deferUpdateReminder,
|
|
skipAvailableUpdateVersion,
|
|
installDownloadedUpdateNow,
|
|
},
|
|
);
|
|
}, delayMs);
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
export function skipAvailableUpdateVersion(sendToRenderer?: UpdateToastSender) {
|
|
const versionToSkip = updaterState.pendingDownloadedVersion ?? updaterState.availableVersion;
|
|
if (!versionToSkip) {
|
|
return { success: false, message: "No update is available to skip." };
|
|
}
|
|
|
|
updaterState.skippedVersion = versionToSkip;
|
|
if (updaterState.pendingDownloadedVersion === versionToSkip) {
|
|
updaterState.pendingDownloadedVersion = null;
|
|
}
|
|
if (updaterState.availableVersion === versionToSkip) {
|
|
updaterState.availableVersion = null;
|
|
}
|
|
updaterState.downloadInProgress = false;
|
|
updaterState.downloadToastDismissed = false;
|
|
clearDeferredReminderTimer();
|
|
clearVisibleUpdateToast(sendToRenderer);
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
export function previewUpdateToast(sendToRenderer: UpdateToastSender) {
|
|
clearDeferredReminderTimer();
|
|
clearDevPreviewProgressTimer();
|
|
updaterState.availableVersion = DEV_UPDATE_PREVIEW_VERSION;
|
|
updaterState.pendingDownloadedVersion = null;
|
|
updaterState.downloadInProgress = false;
|
|
updaterState.downloadToastDismissed = false;
|
|
return emitUpdateToastState(sendToRenderer, {
|
|
version: DEV_UPDATE_PREVIEW_VERSION,
|
|
phase: "available",
|
|
detail: "This is a development preview of the in-app update toast.",
|
|
delayMs: UPDATE_REMINDER_DELAY_MS,
|
|
isPreview: true,
|
|
});
|
|
}
|
|
|
|
export async function checkForAppUpdates(
|
|
getMainWindow: GetMainWindow,
|
|
options?: { manual?: boolean },
|
|
) {
|
|
if (!canUseAutoUpdates()) {
|
|
writeUpdaterLog(
|
|
`Skipped update check because auto-updates are unavailable. packaged=${process.env.NODE_ENV === "production" ? "yes" : "no"} mas=${process.mas ? "yes" : "no"} disabled=${AUTO_UPDATES_DISABLED ? "yes" : "no"}`,
|
|
);
|
|
if (options?.manual) {
|
|
await showMessageBox(getMainWindow, {
|
|
type: "info",
|
|
title: "Updates Not Enabled",
|
|
message: "Auto-updates are only available in packaged releases.",
|
|
detail: AUTO_UPDATES_DISABLED
|
|
? "This build disabled auto-updates through RECORDLY_DISABLE_AUTO_UPDATES=1."
|
|
: "Development builds do not ship the packaged update metadata required by electron-updater.",
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (updaterState.updateCheckInProgress) {
|
|
writeUpdaterLog("Skipped update check because a previous check is still running.");
|
|
if (options?.manual) {
|
|
await showMessageBox(getMainWindow, {
|
|
type: "info",
|
|
title: "Update Check In Progress",
|
|
message: "Recordly is already checking for updates.",
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
updaterState.manualCheckRequested = Boolean(options?.manual);
|
|
updaterState.updateCheckInProgress = true;
|
|
updaterState.updateCheckErrorHandled = false;
|
|
setUpdateStatusSummary({ status: "checking", detail: "Checking for updates..." });
|
|
writeUpdaterLog(
|
|
`Starting ${updaterState.manualCheckRequested ? "manual" : "automatic"} update check.`,
|
|
);
|
|
|
|
try {
|
|
await autoUpdater.checkForUpdates();
|
|
writeUpdaterLog("Update check request completed.");
|
|
} catch (error) {
|
|
updaterState.updateCheckInProgress = false;
|
|
const shouldReport = updaterState.manualCheckRequested;
|
|
updaterState.manualCheckRequested = false;
|
|
setUpdateStatusSummary({
|
|
status: "error",
|
|
availableVersion: updaterState.availableVersion,
|
|
detail: String(error),
|
|
});
|
|
writeUpdaterLog("Update check failed.", error);
|
|
console.error("Auto-update check failed:", error);
|
|
if (shouldReport && !updaterState.updateCheckErrorHandled) {
|
|
await showUpdateErrorDialog(getMainWindow, error);
|
|
} else if (!updaterState.updateCheckErrorHandled && shouldSurfaceAutomaticCheckErrors()) {
|
|
emitUpdateToastState(updaterState.activeUpdateToastSender, createAutoCheckErrorToastPayload());
|
|
}
|
|
}
|
|
}
|
|
|
|
export function setupAutoUpdates(
|
|
getMainWindow: GetMainWindow,
|
|
sendToRenderer: UpdateToastSender,
|
|
) {
|
|
if (updaterState.updaterInitialized) {
|
|
return;
|
|
}
|
|
|
|
if (!canUseAutoUpdates()) {
|
|
setUpdateStatusSummary({ status: "idle", availableVersion: null, detail: undefined });
|
|
return;
|
|
}
|
|
|
|
updaterState.updaterInitialized = true;
|
|
updaterState.activeUpdateToastSender = sendToRenderer;
|
|
configureUpdateFeed();
|
|
autoUpdater.autoDownload = false;
|
|
autoUpdater.autoInstallOnAppQuit = false;
|
|
writeUpdaterLog(`Updater initialized. logPath=${UPDATER_LOG_PATH}`);
|
|
|
|
registerAutoUpdaterEventHandlers(
|
|
getMainWindow,
|
|
sendToRenderer,
|
|
{
|
|
showNoUpdatesDialog,
|
|
showUpdateErrorDialog,
|
|
showAvailableUpdateDialog: (windowGetter, version, renderer) =>
|
|
showAvailableUpdateDialog(windowGetter, version, renderer, {
|
|
downloadAvailableUpdate,
|
|
deferUpdateReminder,
|
|
skipAvailableUpdateVersion,
|
|
installDownloadedUpdateNow,
|
|
}),
|
|
showDownloadedUpdateDialog: (windowGetter, version) =>
|
|
showDownloadedUpdateDialog(windowGetter, version, {
|
|
downloadAvailableUpdate,
|
|
deferUpdateReminder,
|
|
skipAvailableUpdateVersion,
|
|
installDownloadedUpdateNow,
|
|
}),
|
|
},
|
|
checkForAppUpdates,
|
|
);
|
|
} |