Skip to content

Commit fc5e110

Browse files
handle image
1 parent dd471ca commit fc5e110

File tree

9 files changed

+437
-46
lines changed

9 files changed

+437
-46
lines changed

apps/studio/electron/main/pages/update.ts

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,22 +87,94 @@ export async function updateNextJsPage(projectRoot: string, pagePath: string, me
8787
Object.entries(metadata).map(([key, value]) => {
8888
if (typeof value === 'string') {
8989
return t.objectProperty(t.identifier(key), t.stringLiteral(value));
90+
} else if (value === null) {
91+
return t.objectProperty(t.identifier(key), t.nullLiteral());
9092
} else if (Array.isArray(value)) {
9193
return t.objectProperty(
9294
t.identifier(key),
93-
t.arrayExpression(value.map((v) => t.stringLiteral(v))),
95+
t.arrayExpression(
96+
value.map((v) => {
97+
if (typeof v === 'string') {
98+
return t.stringLiteral(v);
99+
} else if (typeof v === 'object' && v !== null) {
100+
return t.objectExpression(
101+
Object.entries(v).map(([k, val]) => {
102+
if (typeof val === 'string') {
103+
return t.objectProperty(
104+
t.identifier(k),
105+
t.stringLiteral(val),
106+
);
107+
} else if (typeof val === 'number') {
108+
return t.objectProperty(
109+
t.identifier(k),
110+
t.numericLiteral(val),
111+
);
112+
}
113+
return t.objectProperty(
114+
t.identifier(k),
115+
t.stringLiteral(String(val)),
116+
);
117+
}),
118+
);
119+
}
120+
return t.stringLiteral(String(v));
121+
}),
122+
),
94123
);
95-
} else if (value === null) {
96-
return t.objectProperty(t.identifier(key), t.nullLiteral());
97-
}
98-
return t.objectProperty(
99-
t.identifier(key),
100-
t.objectExpression(
101-
Object.entries(value).map(([k, v]) =>
102-
t.objectProperty(t.identifier(k), t.stringLiteral(v as string)),
124+
} else if (typeof value === 'object' && value !== null) {
125+
return t.objectProperty(
126+
t.identifier(key),
127+
t.objectExpression(
128+
Object.entries(value).map(([k, v]) => {
129+
if (typeof v === 'string') {
130+
return t.objectProperty(t.identifier(k), t.stringLiteral(v));
131+
} else if (typeof v === 'number') {
132+
return t.objectProperty(t.identifier(k), t.numericLiteral(v));
133+
} else if (Array.isArray(v)) {
134+
return t.objectProperty(
135+
t.identifier(k),
136+
t.arrayExpression(
137+
v.map((item) => {
138+
if (typeof item === 'string') {
139+
return t.stringLiteral(item);
140+
} else if (
141+
typeof item === 'object' &&
142+
item !== null
143+
) {
144+
return t.objectExpression(
145+
Object.entries(item).map(([ik, iv]) => {
146+
if (typeof iv === 'string') {
147+
return t.objectProperty(
148+
t.identifier(ik),
149+
t.stringLiteral(iv),
150+
);
151+
} else if (typeof iv === 'number') {
152+
return t.objectProperty(
153+
t.identifier(ik),
154+
t.numericLiteral(iv),
155+
);
156+
}
157+
return t.objectProperty(
158+
t.identifier(ik),
159+
t.stringLiteral(String(iv)),
160+
);
161+
}),
162+
);
163+
}
164+
return t.stringLiteral(String(item));
165+
}),
166+
),
167+
);
168+
}
169+
return t.objectProperty(
170+
t.identifier(k),
171+
t.stringLiteral(String(v)),
172+
);
173+
}),
103174
),
104-
),
105-
);
175+
);
176+
}
177+
return t.objectProperty(t.identifier(key), t.stringLiteral(String(value)));
106178
}),
107179
);
108180

apps/studio/src/components/Modals/Settings/Site/Favicon.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useCallback, useState } from 'react';
22
import { Button } from '@onlook/ui/button';
33

4-
const Favicon: React.FC<{ onImageSelect: (file: File) => void }> = ({ onImageSelect }) => {
4+
export const Favicon: React.FC<{ onImageSelect: (file: File) => void }> = ({ onImageSelect }) => {
55
const [selectedImage, setSelectedImage] = useState<string | null>(null);
66
const [isDragging, setIsDragging] = useState(false);
77

@@ -46,7 +46,7 @@ const Favicon: React.FC<{ onImageSelect: (file: File) => void }> = ({ onImageSel
4646
};
4747

4848
return (
49-
<div className="flex flex-col gap-2 p-2 text-xs">
49+
<div className="p-2">
5050
<div
5151
className={`group h-16 w-16 bg-background-secondary rounded flex items-center justify-center p-4
5252
${isDragging ? 'border-2 border-dashed border-primary' : ''}`}
@@ -57,30 +57,29 @@ const Favicon: React.FC<{ onImageSelect: (file: File) => void }> = ({ onImageSel
5757
backgroundImage: selectedImage ? `url(${selectedImage})` : 'none',
5858
}}
5959
>
60-
<UploadButton onButtonClick={handleButtonClick} />
6160
<input
6261
type="file"
6362
accept="image/*"
6463
className="hidden"
6564
id="favicon-upload"
6665
onChange={handleFileSelect}
6766
/>
67+
<img src={selectedImage ?? ''} />
6868
</div>
69+
<UploadButton onButtonClick={handleButtonClick} />
6970
</div>
7071
);
7172
};
7273

73-
export const UploadButton: React.FC<{ onButtonClick: (e: React.MouseEvent) => void }> = ({
74+
const UploadButton: React.FC<{ onButtonClick: (e: React.MouseEvent) => void }> = ({
7475
onButtonClick,
7576
}) => (
7677
<Button
77-
variant="secondary"
78-
className="flex items-center gap-2 px-4 py-0 backdrop-blur-sm rounded border border-foreground-tertiary/20 opacity-0 group-hover:opacity-90 transition-opacity"
78+
variant="ghost"
79+
className="flex items-center gap-2 mt-2 px-4 py-0 backdrop-blur-sm rounded border border-foreground-tertiary/20"
7980
type="button"
8081
onClick={onButtonClick}
8182
>
82-
<span>Upload Favicon</span>
83+
<span>Upload Image</span>
8384
</Button>
8485
);
85-
86-
export default Favicon;

apps/studio/src/components/Modals/Settings/Site/Image.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const ImagePicker: React.FC<{ onImageSelect: (file: File) => void }> = ({ onImag
5555
onDrop={handleDrop}
5656
style={{
5757
backgroundImage: selectedImage ? `url(${selectedImage})` : 'none',
58+
backgroundSize: 'cover',
5859
}}
5960
>
6061
<UploadButton onButtonClick={handleButtonClick} />

apps/studio/src/components/Modals/Settings/Site/Page.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import { useEditorEngine, useProjectsManager } from '@/components/Context';
22
import { Input } from '@onlook/ui/input';
33
import { Separator } from '@onlook/ui/separator';
44
import { Textarea } from '@onlook/ui/textarea';
5-
import { observer } from 'mobx-react-lite';
65
import ImagePicker from './Image';
7-
import type { Metadata } from '@onlook/models';
6+
import { DefaultSettings, type Metadata } from '@onlook/models';
87
import { Button } from '@onlook/ui/button';
98
import { memo, useEffect, useState } from 'react';
109

@@ -17,15 +16,14 @@ export const PageTab = memo(({ metadata, path }: { metadata?: Metadata; path: st
1716
const editorEngine = useEditorEngine();
1817
const project = projectsManager.project;
1918

20-
console.log(metadata);
21-
2219
const [title, setTitle] = useState(metadata?.title || DEFAULT_TITLE);
2320
const [description, setDescription] = useState(metadata?.description || DEFAULT_DESCRIPTION);
2421
const [isDirty, setIsDirty] = useState(false);
22+
const [uploadedImage, setUploadedImage] = useState<File | null>(null);
2523

2624
const handleFileSelect = (file: File) => {
27-
// TODO: Handle image upload
28-
console.log('Selected file:', file);
25+
setUploadedImage(file);
26+
setIsDirty(true);
2927
};
3028

3129
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -55,6 +53,28 @@ export const PageTab = memo(({ metadata, path }: { metadata?: Metadata; path: st
5553
description,
5654
};
5755

56+
if (uploadedImage) {
57+
await editorEngine.image.upload(uploadedImage);
58+
const imagePath = `/${DefaultSettings.IMAGE_FOLDER.replace(/^public\//, '')}/${uploadedImage.name}`;
59+
updatedMetadata.metadataBase = new URL(project.domains?.base?.url ?? '');
60+
updatedMetadata.openGraph = {
61+
...updatedMetadata.openGraph,
62+
title: title,
63+
description: description,
64+
url: project.domains?.base?.url || '',
65+
siteName: title,
66+
images: [
67+
{
68+
url: imagePath,
69+
width: 1200,
70+
height: 630,
71+
alt: title,
72+
},
73+
],
74+
type: 'website',
75+
};
76+
}
77+
5878
await editorEngine.pages.updateMetadataPage(path, updatedMetadata, false);
5979
setIsDirty(false);
6080
} catch (error) {

apps/studio/src/components/Modals/Settings/Site/index.tsx

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useEffect, useState } from 'react';
88
import type { Metadata } from '@onlook/models';
99
import { Button } from '@onlook/ui/button';
1010
import { DefaultSettings } from '@onlook/models/constants';
11-
import Favicon from './Favicon';
11+
import { Favicon } from './Favicon';
1212

1313
const DEFAULT_TITLE = 'Saved Places - Discover new spots';
1414
const DEFAULT_DESCRIPTION =
@@ -66,21 +66,22 @@ export const SiteTab = observer(() => {
6666
if (uploadedImage) {
6767
await editorEngine.image.upload(uploadedImage);
6868
const imagePath = `/${DefaultSettings.IMAGE_FOLDER.replace(/^public\//, '')}/${uploadedImage.name}`;
69-
// If icons is already an object, preserve its properties
70-
if (
71-
updatedMetadata.icons &&
72-
typeof updatedMetadata.icons === 'object' &&
73-
!Array.isArray(updatedMetadata.icons)
74-
) {
75-
updatedMetadata.icons = {
76-
...updatedMetadata.icons,
77-
apple: imagePath, // Using apple icon for social media preview
78-
};
79-
} else {
80-
updatedMetadata.icons = {
81-
apple: imagePath, // Using apple icon for social media preview
82-
};
83-
}
69+
updatedMetadata.openGraph = {
70+
...updatedMetadata.openGraph,
71+
title: title,
72+
description: description,
73+
url: project?.url || '',
74+
siteName: title,
75+
images: [
76+
{
77+
url: imagePath,
78+
width: 1200,
79+
height: 630,
80+
alt: title,
81+
},
82+
],
83+
type: 'website',
84+
};
8485
}
8586

8687
projectsManager.updatePartialProject({
@@ -161,7 +162,9 @@ export const SiteTab = observer(() => {
161162
<div className="flex flex-col gap-2">
162163
<h2 className="text-regular text-foreground-onlook">Search Engine Preview</h2>
163164
<div className="bg-background/50 p-4 rounded-md border gap-1.5 flex flex-col">
164-
<p className="text-miniPlus text-blue-500">{project?.url}</p>
165+
<p className="text-miniPlus text-blue-500">
166+
{project?.domains?.base?.url ?? project?.url}
167+
</p>
165168
<h3 className="text-regular">{title || DEFAULT_TITLE}</h3>
166169
<p className="text-sm text-muted-foreground line-clamp-2">
167170
{description || DEFAULT_DESCRIPTION}

apps/studio/src/components/Modals/Settings/index.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { VersionsTab } from './Versions';
1414
import { capitalizeFirstLetter } from '/common/helpers';
1515
import { SiteTab } from './Site';
1616
import { PageTab } from './Site/Page';
17+
import { Tooltip, TooltipContent, TooltipTrigger } from '@onlook/ui/tooltip';
18+
import { useEffect } from 'react';
1719

1820
interface SettingTab {
1921
label: SettingsTabValue | string;
@@ -45,7 +47,7 @@ export const SettingsModal = observer(() => {
4547
const projectOnlyTabs: SettingTab[] = [
4648
{
4749
label: SettingsTabValue.SITE,
48-
icon: <Icons.Globe className="mr-2 h-4 w-4" />,
50+
icon: <Icons.File className="mr-2 h-4 w-4" />,
4951
component: <SiteTab />,
5052
},
5153
{
@@ -79,13 +81,17 @@ export const SettingsModal = observer(() => {
7981
];
8082

8183
const pagesTabs: SettingTab[] = flattenPages.map((page) => ({
82-
label: page.path,
84+
label: page.path === '/' ? 'Home' : page.path,
8385
icon: <Icons.File className="mr-2 h-4 min-w-4" />,
8486
component: <PageTab metadata={page.metadata} path={page.path} />,
8587
}));
8688

8789
const tabs = project ? [...projectOnlyTabs, ...globalTabs, ...pagesTabs] : [...globalTabs];
8890

91+
useEffect(() => {
92+
editorEngine.pages.scanPages();
93+
}, []);
94+
8995
return (
9096
<AnimatePresence>
9197
{editorEngine.isSettingsOpen && (
@@ -171,7 +177,20 @@ export const SettingsModal = observer(() => {
171177
}
172178
>
173179
{tab.icon}
174-
{capitalizeFirstLetter(tab.label.toLowerCase())}
180+
<Tooltip>
181+
<TooltipTrigger asChild>
182+
<span className="truncate">
183+
{capitalizeFirstLetter(
184+
tab.label.toLowerCase(),
185+
)}
186+
</span>
187+
</TooltipTrigger>
188+
<TooltipContent>
189+
{capitalizeFirstLetter(
190+
tab.label.toLowerCase(),
191+
)}
192+
</TooltipContent>
193+
</Tooltip>
175194
</Button>
176195
))}
177196
</div>

apps/studio/src/lib/editor/engine/pages/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ export class PagesManager {
252252
// throw new Error('A page with this name does not exists');
253253
// }
254254

255+
console.log(projectRoot, pagePath, metadata, isRoot);
256+
255257
try {
256258
await invokeMainChannel(MainChannels.UPDATE_PAGE_METADATA, {
257259
projectRoot,

packages/models/src/pages/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { OpenGraph } from './opengraph';
2+
13
export interface PageNode {
24
id: string;
35
path: string;
@@ -12,7 +14,9 @@ export interface Metadata {
1214
title?: string;
1315
description?: string;
1416
applicationName?: string;
17+
metadataBase?: null | URL;
1518
icons?: null | IconURL | Array<Icon> | Icons;
19+
openGraph?: null | OpenGraph;
1620
}
1721

1822
type IconURL = string | URL;

0 commit comments

Comments
 (0)