Skip to content

Commit 1365893

Browse files
refactor: allow undo redo action with font (onlook-dev#1751)
* refactor: allow undo redo action with font * add listener * allow undo redo default font * add font weight * add log error when can not read file content
1 parent 1fcb0e5 commit 1365893

File tree

6 files changed

+185
-58
lines changed

6 files changed

+185
-58
lines changed

apps/studio/electron/main/assets/fonts/font.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import generate from '@babel/generator';
99
import { formatContent, readFile, writeFile } from '../../code/files';
1010
import fs from 'fs';
1111
import { extractFontParts, getFontFileName } from '@onlook/utility';
12+
import type { CodeDiff } from '@onlook/models';
1213

1314
/**
1415
* Adds a new font to the project by:
@@ -129,9 +130,12 @@ export async function addFont(projectRoot: string, font: Font) {
129130
// Generate the new code from the AST
130131
const { code } = generate(ast);
131132

132-
// Write the updated content back to the file
133-
const formattedCode = await formatContent(fontPath, code);
134-
await writeFile(fontPath, formattedCode);
133+
const codeDiff: CodeDiff = {
134+
original: content,
135+
generated: code,
136+
path: fontPath,
137+
};
138+
return codeDiff;
135139
} catch (error) {
136140
console.error('Error adding font:', error);
137141
}
@@ -278,25 +282,30 @@ export async function removeFont(projectRoot: string, font: Font) {
278282
/import\s+localFont\s+from\s+['"]next\/font\/local['"];\n?/g;
279283
code = code.replace(localFontImportRegex, '');
280284
}
281-
const formattedCode = await formatContent(fontPath, code);
282-
await writeFile(fontPath, formattedCode);
285+
const codeDiff: CodeDiff = {
286+
original: content,
287+
generated: code,
288+
path: fontPath,
289+
};
283290

284291
// Delete font files if found
285-
if (fontFilesToDelete.length > 0) {
286-
for (const fileRelativePath of fontFilesToDelete) {
287-
const absoluteFilePath = pathModule.join(projectRoot, fileRelativePath);
288-
if (fs.existsSync(absoluteFilePath)) {
289-
try {
290-
fs.unlinkSync(absoluteFilePath);
291-
console.log(`Deleted font file: ${absoluteFilePath}`);
292-
} catch (error) {
293-
console.error(`Error deleting font file ${absoluteFilePath}:`, error);
294-
}
295-
} else {
296-
console.log(`Font file not found: ${absoluteFilePath}`);
297-
}
298-
}
299-
}
292+
// if (fontFilesToDelete.length > 0) {
293+
// for (const fileRelativePath of fontFilesToDelete) {
294+
// const absoluteFilePath = pathModule.join(projectRoot, fileRelativePath);
295+
// if (fs.existsSync(absoluteFilePath)) {
296+
// try {
297+
// fs.unlinkSync(absoluteFilePath);
298+
// console.log(`Deleted font file: ${absoluteFilePath}`);
299+
// } catch (error) {
300+
// console.error(`Error deleting font file ${absoluteFilePath}:`, error);
301+
// }
302+
// } else {
303+
// console.log(`Font file not found: ${absoluteFilePath}`);
304+
// }
305+
// }
306+
// }
307+
// Commented out for now—since we have undo/redo functionality for adding/removing fonts, we shouldn’t delete these files to ensure proper rollback support.
308+
return codeDiff;
300309
} else {
301310
console.log(`Font ${fontIdToRemove} not found in font.ts`);
302311
}

apps/studio/electron/main/assets/fonts/layout.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
removeFontsFromClassName,
1616
} from './utils';
1717
import type { TraverseCallback } from './types';
18+
import type { CodeDiff } from '@onlook/models/code';
1819

1920
/**
2021
* Traverses JSX elements in a file to find and modify className attributes
@@ -33,6 +34,7 @@ export async function traverseClassName(
3334

3435
const content = await readFile(filePath);
3536
if (!content) {
37+
console.error(`Failed to read file: ${filePath}`);
3638
return;
3739
}
3840

@@ -124,6 +126,7 @@ export async function addFontVariableToElement(
124126

125127
const content = await readFile(filePath);
126128
if (!content) {
129+
console.error(`Failed to read file: ${filePath}`);
127130
return;
128131
}
129132

@@ -235,6 +238,7 @@ export async function removeFontVariableFromLayout(
235238

236239
const content = await readFile(filePath);
237240
if (!content) {
241+
console.error(`Failed to read file: ${filePath}`);
238242
return;
239243
}
240244

@@ -293,11 +297,17 @@ export async function updateFontInLayout(
293297
filePath: string,
294298
font: Font,
295299
targetElements: string[],
296-
): Promise<void> {
300+
): Promise<null | CodeDiff> {
297301
let updatedAst = false;
298302
const fontClassName = `font-${font.id}`;
299303
let result = null;
300304

305+
const content = await readFile(filePath);
306+
if (!content) {
307+
console.error(`Failed to read file: ${filePath}`);
308+
return null;
309+
}
310+
301311
await traverseClassName(filePath, targetElements, (classNameAttr, ast) => {
302312
if (t.isStringLiteral(classNameAttr.value)) {
303313
classNameAttr.value = createStringLiteralWithFont(
@@ -322,13 +332,16 @@ export async function updateFontInLayout(
322332
}
323333
if (updatedAst) {
324334
const { code } = generate(ast);
325-
result = code;
335+
const codeDiff: CodeDiff = {
336+
original: content,
337+
generated: code,
338+
path: filePath,
339+
};
340+
result = codeDiff;
326341
}
327342
});
328343

329-
if (result) {
330-
fs.writeFileSync(filePath, result);
331-
}
344+
return result;
332345
}
333346

334347
/**
@@ -399,10 +412,10 @@ export async function setDefaultFont(projectRoot: string, font: Font) {
399412

400413
if (routerConfig.type === 'app') {
401414
const layoutPath = pathModule.join(routerConfig.basePath, 'layout.tsx');
402-
await updateFontInLayout(layoutPath, font, ['html']);
415+
return await updateFontInLayout(layoutPath, font, ['html']);
403416
} else {
404417
const appPath = pathModule.join(routerConfig.basePath, '_app.tsx');
405-
await updateFontInLayout(appPath, font, ['div', 'main', 'section', 'body']);
418+
return await updateFontInLayout(appPath, font, ['div', 'main', 'section', 'body']);
406419
}
407420
} catch (error) {
408421
console.error('Error setting default font:', error);

apps/studio/electron/main/assets/fonts/watcher.ts

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { subscribe, type AsyncSubscription } from '@parcel/watcher';
2-
import { DefaultSettings } from '@onlook/models/constants';
2+
import { DefaultSettings, MainChannels } from '@onlook/models/constants';
33
import * as pathModule from 'path';
44
import { scanFonts } from './scanner';
55
import fs from 'fs';
@@ -8,16 +8,20 @@ import { removeFontVariableFromLayout } from './layout';
88
import { removeFontFromTailwindConfig, updateTailwindFontConfig } from './tailwind';
99
import type { Font } from '@onlook/models/assets';
1010
import { detectRouterType } from '../../pages';
11+
import { mainWindow } from '../../index';
1112

1213
export class FontFileWatcher {
1314
private subscription: AsyncSubscription | null = null;
1415
private previousFonts: Font[] = [];
16+
private selfModified: Set<string> = new Set();
1517

1618
async watch(projectRoot: string) {
1719
await this.clearSubscription();
1820

19-
const fontPath = pathModule.resolve(projectRoot, DefaultSettings.FONT_CONFIG);
20-
const fontDir = pathModule.dirname(fontPath);
21+
const _fontPath = pathModule.resolve(projectRoot, DefaultSettings.FONT_CONFIG);
22+
const fontDir = pathModule.dirname(_fontPath);
23+
let _layoutPath: string = '';
24+
let _appPath: string = '';
2125

2226
if (!fs.existsSync(fontDir)) {
2327
console.error(`Font directory does not exist: ${fontDir}`);
@@ -30,6 +34,15 @@ export class FontFileWatcher {
3034
this.previousFonts = [];
3135
}
3236

37+
const routerConfig = await detectRouterType(projectRoot);
38+
if (routerConfig) {
39+
if (routerConfig.type === 'app') {
40+
_layoutPath = pathModule.join(routerConfig.basePath, 'layout.tsx');
41+
} else {
42+
_appPath = pathModule.join(routerConfig.basePath, '_app.tsx');
43+
}
44+
}
45+
3346
try {
3447
this.subscription = await subscribe(
3548
fontDir,
@@ -42,10 +55,16 @@ export class FontFileWatcher {
4255
if (events.length > 0) {
4356
for (const event of events) {
4457
const eventPath = pathModule.normalize(event.path);
45-
const expectedPath = pathModule.normalize(fontPath);
58+
const fontPath = pathModule.normalize(_fontPath);
59+
const layoutPath = pathModule.normalize(_layoutPath);
60+
const appPath = pathModule.normalize(_appPath);
61+
if (this.selfModified.has(eventPath)) {
62+
this.selfModified.delete(eventPath);
63+
continue;
64+
}
4665

4766
if (
48-
(eventPath === expectedPath ||
67+
(eventPath === fontPath ||
4968
eventPath.endsWith(DefaultSettings.FONT_CONFIG)) &&
5069
(event.type === 'update' || event.type === 'create')
5170
) {
@@ -55,6 +74,17 @@ export class FontFileWatcher {
5574
console.error('Error syncing fonts with configs:', error);
5675
}
5776
}
77+
if (
78+
(eventPath === layoutPath || eventPath === appPath) &&
79+
(event.type === 'update' || event.type === 'create')
80+
) {
81+
this.selfModified.add(eventPath);
82+
try {
83+
mainWindow?.webContents.send(MainChannels.GET_DEFAULT_FONT);
84+
} catch (error) {
85+
console.error('Error syncing fonts with configs:', error);
86+
}
87+
}
5888
}
5989
}
6090
},
@@ -78,17 +108,19 @@ export class FontFileWatcher {
78108
);
79109

80110
const addedFonts = currentFonts.filter(
81-
(currFont) => !this.previousFonts.some((prevFont) => prevFont.id === currFont.id),
111+
(currFont) => !this.previousFonts.some((prevFont) => currFont.id === prevFont.id),
82112
);
83113

84114
for (const font of removedFonts) {
85115
const routerConfig = await detectRouterType(projectRoot);
86116
if (routerConfig) {
87117
if (routerConfig.type === 'app') {
88118
const layoutPath = pathModule.join(routerConfig.basePath, 'layout.tsx');
119+
this.selfModified.add(layoutPath);
89120
await removeFontVariableFromLayout(layoutPath, font.id, ['html']);
90121
} else {
91122
const appPath = pathModule.join(routerConfig.basePath, '_app.tsx');
123+
this.selfModified.add(appPath);
92124
await removeFontVariableFromLayout(appPath, font.id, [
93125
'div',
94126
'main',
@@ -98,16 +130,40 @@ export class FontFileWatcher {
98130
}
99131
}
100132

133+
const tailwindConfigPath = pathModule.join(projectRoot, 'tailwind.config.ts');
134+
this.selfModified.add(tailwindConfigPath);
101135
await removeFontFromTailwindConfig(projectRoot, font);
102136
}
103137

104138
if (addedFonts.length > 0) {
105139
for (const font of addedFonts) {
140+
const tailwindConfigPath = pathModule.join(projectRoot, 'tailwind.config.ts');
141+
this.selfModified.add(tailwindConfigPath);
106142
await updateTailwindFontConfig(projectRoot, font);
107-
await addFontVariableToLayout(projectRoot, font.id);
143+
144+
const routerConfig = await detectRouterType(projectRoot);
145+
if (routerConfig) {
146+
if (routerConfig.type === 'app') {
147+
const layoutPath = pathModule.join(routerConfig.basePath, 'layout.tsx');
148+
this.selfModified.add(layoutPath);
149+
await addFontVariableToLayout(projectRoot, font.id);
150+
} else {
151+
const appPath = pathModule.join(routerConfig.basePath, '_app.tsx');
152+
this.selfModified.add(appPath);
153+
await addFontVariableToLayout(projectRoot, font.id);
154+
}
155+
}
108156
}
109157
}
110158

159+
if (removedFonts.length > 0 || addedFonts.length > 0) {
160+
mainWindow?.webContents.send(MainChannels.FONTS_CHANGED, {
161+
currentFonts,
162+
removedFonts,
163+
addedFonts,
164+
});
165+
}
166+
111167
this.previousFonts = currentFonts;
112168
} catch (error) {
113169
console.error('Error syncing fonts:', error);

0 commit comments

Comments
 (0)