Add foundational components and logic for migration, UI design, and input handling
- Introduced foundational UI components (`Badge`, `LockCard`, `SectionHeader`, `AvatarIcon`, etc.) for flexible layouts and consistent design. - Added migration support with the `MigrationModal` component and backend integration for exporting/importing data between Electron and Tauri. - Extended form components with `TextAreaInput`, `OrderInput`, `ToggleField`, and `ToolbarSelect` for improved input handling. - Updated `ScribeShell` with migration popup logic to prompt users for data migration. - Integrated `AlertStack` for better alert handling and notification management. - Enhanced Rust/Tauri services with migration command implementations. - Added translations and styles for new components.
This commit is contained in:
101
lib/crashReporter.ts
Normal file
101
lib/crashReporter.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import axios from 'axios';
|
||||
import {configs, isDesktop} from '@/lib/configs';
|
||||
|
||||
interface CrashReportPayload {
|
||||
appName: string;
|
||||
appVersion: string;
|
||||
platform: string;
|
||||
osVersion?: string;
|
||||
errorType: string;
|
||||
errorMessage: string;
|
||||
stackTrace?: string;
|
||||
screenName?: string;
|
||||
userId?: string;
|
||||
breadcrumbs?: Breadcrumb[];
|
||||
extraData?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface Breadcrumb {
|
||||
timestamp: number;
|
||||
action: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
const MAX_BREADCRUMBS = 30;
|
||||
const breadcrumbs: Breadcrumb[] = [];
|
||||
|
||||
export function addBreadcrumb(action: string, detail?: string): void {
|
||||
breadcrumbs.push({timestamp: Date.now(), action, detail});
|
||||
if (breadcrumbs.length > MAX_BREADCRUMBS) breadcrumbs.shift();
|
||||
}
|
||||
|
||||
function getPlatform(): string {
|
||||
if (!isDesktop) return 'web';
|
||||
const ua: string = navigator.userAgent.toLowerCase();
|
||||
if (ua.includes('mac')) return 'desktop-macos';
|
||||
if (ua.includes('win')) return 'desktop-windows';
|
||||
if (ua.includes('linux')) return 'desktop-linux';
|
||||
return 'desktop';
|
||||
}
|
||||
|
||||
function getUserId(): string | undefined {
|
||||
try {
|
||||
const raw: string | null = localStorage.getItem('userId');
|
||||
return raw ?? undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function sendCrashReport(payload: CrashReportPayload): Promise<void> {
|
||||
try {
|
||||
await axios.post(configs.apiUrl + 'crash-report', payload, {
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch {
|
||||
// Silently fail — we can't crash while reporting a crash
|
||||
}
|
||||
}
|
||||
|
||||
function buildReport(errorType: string, errorMessage: string, stackTrace?: string): CrashReportPayload {
|
||||
return {
|
||||
appName: configs.appName,
|
||||
appVersion: configs.appVersion,
|
||||
platform: getPlatform(),
|
||||
osVersion: navigator.userAgent,
|
||||
errorType,
|
||||
errorMessage,
|
||||
stackTrace,
|
||||
screenName: window.location.pathname,
|
||||
userId: getUserId(),
|
||||
breadcrumbs: [...breadcrumbs],
|
||||
};
|
||||
}
|
||||
|
||||
export function reportError(error: Error, extraData?: Record<string, unknown>): void {
|
||||
const report: CrashReportPayload = buildReport(
|
||||
error.name || 'Error',
|
||||
error.message,
|
||||
error.stack,
|
||||
);
|
||||
if (extraData) report.extraData = extraData;
|
||||
sendCrashReport(report);
|
||||
}
|
||||
|
||||
export function initCrashReporter(): void {
|
||||
window.onerror = (_message, _source, _lineno, _colno, error: Error | undefined): void => {
|
||||
if (error) {
|
||||
sendCrashReport(buildReport('UncaughtError', error.message, error.stack));
|
||||
}
|
||||
};
|
||||
|
||||
window.onunhandledrejection = (event: PromiseRejectionEvent): void => {
|
||||
const reason: unknown = event.reason;
|
||||
if (reason instanceof Error) {
|
||||
sendCrashReport(buildReport('UnhandledRejection', reason.message, reason.stack));
|
||||
} else {
|
||||
sendCrashReport(buildReport('UnhandledRejection', String(reason)));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -187,9 +187,38 @@
|
||||
"title": "Discord",
|
||||
"description": "Join our community on Discord.",
|
||||
"badge": "DISCORD"
|
||||
},
|
||||
"migration": {
|
||||
"title": "Import from Electron",
|
||||
"description": "Migrate your data from the old version."
|
||||
}
|
||||
}
|
||||
},
|
||||
"migration": {
|
||||
"title": "Migration from Electron",
|
||||
"introText": "We detected this is your first launch. If you were using the old version of ERitors Scribe (Electron), you can import your local data (books, characters, chapters, etc.).",
|
||||
"steps": "How to proceed:",
|
||||
"step1": "In the old Electron app, go to the menu and click \"Export for migration\".",
|
||||
"step2": "A .json file and a copy of your database will be created on your Desktop.",
|
||||
"step3": "Come back here and enter the path to the exported .json file.",
|
||||
"later": "Later",
|
||||
"haveFile": "I have the file",
|
||||
"selectText": "Paste the full path to the migration file exported from Electron.",
|
||||
"filePath": "Migration file path",
|
||||
"back": "Back",
|
||||
"import": "Import",
|
||||
"importing": "Migrating...",
|
||||
"successTitle": "Migration successful!",
|
||||
"successText": "Your data has been imported successfully. Log in again to access it.",
|
||||
"deleteReminder": "Remember to delete the migration file and the database copy from your Desktop.",
|
||||
"done": "Done",
|
||||
"errorTitle": "Migration error",
|
||||
"close": "Close",
|
||||
"retry": "Retry",
|
||||
"fileNotFound": "The migration file was not found at this path.",
|
||||
"dbNotFound": "The database was not found next to the migration file.",
|
||||
"importFailed": "Import failed. Please check that the files are valid."
|
||||
},
|
||||
"quillSense": {
|
||||
"needSubscription": "Please subscribe to QuillSense or bring your keys to access this feature.",
|
||||
"subscriptionDescription": "Unlock powerful writing tools to enrich your prose.",
|
||||
|
||||
@@ -187,9 +187,38 @@
|
||||
"title": "Discord",
|
||||
"description": "Rejoignez notre communauté sur Discord.",
|
||||
"badge": "DISCORD"
|
||||
},
|
||||
"migration": {
|
||||
"title": "Importer depuis Electron",
|
||||
"description": "Migrer vos données depuis l'ancienne version."
|
||||
}
|
||||
}
|
||||
},
|
||||
"migration": {
|
||||
"title": "Migration depuis Electron",
|
||||
"introText": "Nous avons détecté que c'est votre premier lancement. Si vous utilisiez l'ancienne version d'ERitors Scribe (Electron), vous pouvez importer vos données locales (livres, personnages, chapitres, etc.).",
|
||||
"steps": "Comment procéder :",
|
||||
"step1": "Dans l'ancienne app Electron, allez dans le menu et cliquez sur « Exporter pour migration ».",
|
||||
"step2": "Un fichier .json et une copie de votre base de données seront créés sur votre Bureau.",
|
||||
"step3": "Revenez ici et indiquez le chemin du fichier .json exporté.",
|
||||
"later": "Plus tard",
|
||||
"haveFile": "J'ai le fichier",
|
||||
"selectText": "Collez le chemin complet du fichier de migration exporté depuis Electron.",
|
||||
"filePath": "Chemin du fichier de migration",
|
||||
"back": "Retour",
|
||||
"import": "Importer",
|
||||
"importing": "Migration en cours...",
|
||||
"successTitle": "Migration réussie !",
|
||||
"successText": "Vos données ont été importées avec succès. Reconnectez-vous pour y accéder.",
|
||||
"deleteReminder": "Pensez à supprimer le fichier de migration et la copie de la base de données de votre Bureau.",
|
||||
"done": "Terminé",
|
||||
"errorTitle": "Erreur de migration",
|
||||
"close": "Fermer",
|
||||
"retry": "Réessayer",
|
||||
"fileNotFound": "Le fichier de migration est introuvable à ce chemin.",
|
||||
"dbNotFound": "La base de données n'a pas été trouvée à côté du fichier de migration.",
|
||||
"importFailed": "L'importation a échoué. Vérifiez que les fichiers sont valides."
|
||||
},
|
||||
"quillSense": {
|
||||
"needSubscription": "Veuillez vous abonner à QuillSense ou Amenez vos clés pour accéder à cette fonctionnalité.",
|
||||
"subscriptionDescription": "Débloquez des outils d'aide à l'écriture puissants pour enrichir votre prose.",
|
||||
|
||||
23
lib/tauri.ts
23
lib/tauri.ts
@@ -677,6 +677,29 @@ export async function applySeriesTombstones(tombstones: TombstoneRecord[]): Prom
|
||||
return invoke<void>('apply_series_tombstones', {tombstones});
|
||||
}
|
||||
|
||||
// ─── Migration ────────────────────────────────────────────────
|
||||
|
||||
export interface MigrationCheckResult {
|
||||
found: boolean;
|
||||
userId: string | null;
|
||||
hasDb: boolean;
|
||||
migrationPath: string | null;
|
||||
}
|
||||
|
||||
export interface MigrationResult {
|
||||
success: boolean;
|
||||
userId: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export async function checkElectronMigration(migrationFilePath: string): Promise<MigrationCheckResult> {
|
||||
return invoke<MigrationCheckResult>('check_electron_migration', {migrationFilePath});
|
||||
}
|
||||
|
||||
export async function importFromElectron(migrationFilePath: string): Promise<MigrationResult> {
|
||||
return invoke<MigrationResult>('import_from_electron', {data: {migrationFilePath}});
|
||||
}
|
||||
|
||||
// ─── Window Management ──────────────────────────────────────
|
||||
|
||||
let loginWindowOpening = false;
|
||||
|
||||
4
lib/utils/webkitDetect.ts
Normal file
4
lib/utils/webkitDetect.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export function isWebKitWithoutIndentFix(): boolean {
|
||||
const ua: string = navigator.userAgent;
|
||||
return /AppleWebKit/.test(ua) && !/Chrome|Chromium|Edg/.test(ua);
|
||||
}
|
||||
Reference in New Issue
Block a user