Skip to content
Merged

Fix #20

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/web/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
"editor_error_title": "Chyba při načítání souboru",
"project_download_no_files": "V projektu nebyly nalezeny žádné soubory ke stažení",
"project_layout_load_error": "Nepodařilo se načíst nastavení rozložení, používá se výchozí",
"project_save_before_leave_error": "Projektové soubory se nepodařilo uložit. Navigace byla zastavena, aby se změny neztratily.",
"project_provider_load_failed": "Nepodařilo se načíst projekt",
"project_panel_pkg_load_error": "Nepodařilo se načíst knihovny",
"project_panel_pkg_versions_error": "Nepodařilo se načíst verze knihovny",
Expand Down Expand Up @@ -235,6 +236,14 @@
"editor_jacly_autosave_error": "Nepodařilo se uložit automatickou zálohu projektu",
"editor_jacly_save_json_error": "Nepodařilo se uložit JSON",
"editor_jacly_save_code_error": "Nepodařilo se uložit generovaný kód",
"editor_jacly_recovery_title": "Soubor bloků je poškozený",
"editor_jacly_recovery_description": "Jacly našel platnou zálohu ({backupName}). Nahradit poškozený soubor index.jacly touto zálohou? Poškozený soubor bude zachován.",
"editor_jacly_recovery_keep": "Ponechat poškozený soubor",
"editor_jacly_recovery_replace": "Nahradit ze zálohy",
"editor_jacly_recovery_restoring": "Obnovuji...",
"editor_jacly_recovery_kept": "Poškozený soubor index.jacly nebyl změněn",
"editor_jacly_recovery_success": "Soubor index.jacly byl obnoven ze zálohy",
"editor_jacly_recovery_error": "Obnovení souboru index.jacly ze zálohy se nezdařilo",
"editor_jacly_missing_packages": "Některé bloky ve vašem workspace vyžadují balíčky, které nejsou aktuálně nainstalovány. Zkontrolujte chybějící balíčky a nainstalujte je.",
"editor_jacly_loading_desc": "Připravuji váš workspace...",
"editor_jacly_installing_header": "Instalace balíčků",
Expand Down
9 changes: 9 additions & 0 deletions apps/web/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@
"editor_jacly_autosave_error": "Failed to save project autosave backup",
"editor_jacly_save_json_error": "Failed to save JSON",
"editor_jacly_save_code_error": "Failed to save generated code",
"editor_jacly_recovery_title": "The blocks file is damaged",
"editor_jacly_recovery_description": "Jacly found a valid backup ({backupName}). Replace the damaged index.jacly file with this backup? The damaged file will be preserved.",
"editor_jacly_recovery_keep": "Keep damaged file",
"editor_jacly_recovery_replace": "Replace from backup",
"editor_jacly_recovery_restoring": "Restoring...",
"editor_jacly_recovery_kept": "The damaged index.jacly file was not changed",
"editor_jacly_recovery_success": "index.jacly was restored from backup",
"editor_jacly_recovery_error": "Failed to restore index.jacly from backup",
"editor_jacly_missing_packages": "Some blocks in your workspace require packages that are not currently installed. Please review the missing packages and install them.",
"editor_jacly_loading_desc": "Preparing your workspace...",
"editor_jacly_installing_header": "Installing Packages",
Expand All @@ -242,6 +250,7 @@
"editor_code_file_update_error": "Error updating file",
"project_download_no_files": "No files found in project to download",
"project_layout_load_error": "Failed to load layout settings, using default",
"project_save_before_leave_error": "Failed to save project files. Navigation was stopped to protect your changes.",
"device_wifi_network_added": "WiFi network added successfully",
"device_wifi_network_add_failed": "Failed to add WiFi network",
"device_wifi_network_removed": "WiFi network removed successfully",
Expand Down
11 changes: 3 additions & 8 deletions apps/web/src/editor/code/code-editor-rw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function CodeEditorRW({ filePath }: CodeEditorRWProps) {
const fullPath = `${projectPath}/${filePath}`;

useEffect(() => {
setLoading(true);
async function loadFile() {
if (!monacoService) return;
await monacoService.requestFile(filePath);
Expand All @@ -29,14 +30,9 @@ export function CodeEditorRW({ filePath }: CodeEditorRWProps) {
loadFile();

return () => {
monacoService?.closeFile(filePath);
void monacoService?.closeFile(filePath);
};
}, [fullPath]);

function handleEditorChange(value: string | undefined) {
if (value === undefined) return;
monacoService?.updateFile(filePath, value);
}
}, [filePath, monacoService]);

if (loading) {
return <div>{m.editor_loading()}</div>;
Expand All @@ -53,7 +49,6 @@ export function CodeEditorRW({ filePath }: CodeEditorRWProps) {
automaticLayout: true,
fixedOverflowWidgets: true,
}}
onChange={handleEditorChange}
/>
);
}
10 changes: 8 additions & 2 deletions apps/web/src/editor/jacly/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export { JaclyEditorComponent } from './jacly';
export { AUTOSAVE_INTERVAL_MS, writeAutosaveBackup, writeStartupBackup } from './jacly-backup';
export {
AUTOSAVE_INTERVAL_MS,
preserveCorruptIndex,
writeAutosaveBackup,
writeStartupBackup,
} from './jacly-backup';
export type { JaclyBackupCandidate } from './jacly-backup-recovery';
export { findNewestValidBackup } from './jacly-backup-recovery';
export type {
EditorJaclyActions,
EditorJaclyContextValue,
Expand All @@ -17,4 +24,3 @@ export {
export { JaclyEditorPanel } from './jacly-panel';
export { EditorJaclyProvider } from './jacly-provider';
export { jaclySaveCoordinator } from './jacly-save-coordinator';
export { createLatestFileWriter, type LatestFileWriter } from './latest-file-writer';
54 changes: 54 additions & 0 deletions apps/web/src/editor/jacly/jacly-backup-recovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { basename, extname, join } from 'node:path';
import type { ProjectFsPromises } from './jacly-files';

const BACKUP_DIR = 'backup';

export interface JaclyBackupCandidate {
path: string;
name: string;
content: string;
json: object;
}

export async function findNewestValidBackup(
fsp: ProjectFsPromises,
projectPath: string,
jaclyPath: string,
): Promise<JaclyBackupCandidate | null> {
const name = basename(jaclyPath, extname(jaclyPath));
const ext = extname(jaclyPath);
const backupDir = join(projectPath, BACKUP_DIR);

try {
const files = (await fsp.readdir(backupDir)) as string[];
const candidates = await Promise.all(
files
.filter(
(file) =>
(file.startsWith(`${name}-A-`) || file.startsWith(`${name}-S-`)) && file.endsWith(ext),
)
.map(async (file) => ({
file,
mtimeMs: (await fsp.stat(join(backupDir, file))).mtimeMs,
})),
);
candidates.sort((left, right) => right.mtimeMs - left.mtimeMs);

for (const { file } of candidates) {
const path = join(backupDir, file);
try {
const content = await fsp.readFile(path, 'utf-8');
const json: unknown = JSON.parse(content);
if (typeof json === 'object' && json !== null && !Array.isArray(json)) {
return { path, name: file, content, json };
}
} catch {
// Continue to older backups when a backup is incomplete or invalid.
}
}
} catch {
return null;
}

return null;
}
14 changes: 12 additions & 2 deletions apps/web/src/editor/jacly/jacly-backup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { basename, extname, join } from 'node:path';
import { durableWriteFile } from '@/project';
import type { ProjectFsPromises } from './jacly-files';

export const AUTOSAVE_INTERVAL_MS = 5 * 60 * 1000;
Expand All @@ -19,7 +20,7 @@ async function writeBackupFile(
projectPath: string,
jaclyPath: string,
content: string,
prefix: 'S' | 'A',
prefix: 'S' | 'A' | 'C',
): Promise<void> {
const name = basename(jaclyPath, extname(jaclyPath));
const ext = extname(jaclyPath);
Expand All @@ -28,7 +29,16 @@ async function writeBackupFile(
await fsp.mkdir(backupDir, { recursive: true });

const fileName = `${name}-${prefix}-${formatTimestamp(new Date())}${ext}`;
await fsp.writeFile(join(backupDir, fileName), content, 'utf-8');
await durableWriteFile(fsp, join(backupDir, fileName), content);
}

export async function preserveCorruptIndex(
fsp: ProjectFsPromises,
projectPath: string,
jaclyPath: string,
content: string,
): Promise<void> {
await writeBackupFile(fsp, projectPath, jaclyPath, content, 'C');
}

async function pruneAutosaveBackups(
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/editor/jacly/jacly-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ export async function readOrCreateJsonFile(
fs: ProjectFs,
fsp: ProjectFsPromises,
filePath: string,
createFile: (path: string, content: string) => Promise<void>,
): Promise<object> {
await ensureParentDir(fsp, filePath);

if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
}

await fsp.writeFile(filePath, '{}', 'utf-8');
await createFile(filePath, '{}');
return {};
}
Loading
Loading