Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/onlook-dev/onlook into tran…
Browse files Browse the repository at this point in the history
…slation_zh

* 'main' of https://github.com/onlook-dev/onlook:
  Fix port taken appearing on error (onlook-dev#1457)
  feat: Add properties panel (onlook-dev#1276)
  Show layers for selected window only (onlook-dev#1446)
  Update import warning (onlook-dev#1456)
  feat: Improve port conflict handling (onlook-dev#1397)
  Verifying and adding custom domain pt.3 (onlook-dev#1455)
  Support custom domain pt.2 (onlook-dev#1454)
  Support publishing to custom domain (onlook-dev#1453)
  Clean up custom domains (onlook-dev#1452)
  Handle creating custom domains (onlook-dev#1447)
  • Loading branch information
克隆(宗可龙) committed Feb 24, 2025
2 parents 5485372 + 5293125 commit 14dfc9d
Show file tree
Hide file tree
Showing 63 changed files with 3,586 additions and 1,438 deletions.
23 changes: 22 additions & 1 deletion apps/studio/electron/main/bun/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { RunBunCommandOptions, RunBunCommandResult } from '@onlook/models';
import type {
DetectedPortResults,
RunBunCommandOptions,
RunBunCommandResult,
} from '@onlook/models';
import { exec } from 'child_process';
import { detect } from 'detect-port';
import { app } from 'electron';
import path from 'path';
import { promisify } from 'util';
Expand Down Expand Up @@ -48,3 +53,19 @@ export const getBunCommand = (command: string): string => {
const bunExecutable = getBunExecutablePath();
return replaceCommand(command, bunExecutable);
};

export async function isPortAvailable(port: number): Promise<DetectedPortResults> {
try {
const availablePort = await detect(port);
return {
isPortAvailable: port === availablePort,
availablePort: availablePort,
};
} catch (error) {
console.error('Error detecting port:', error);
return {
isPortAvailable: false,
availablePort: 3000,
};
}
}
43 changes: 43 additions & 0 deletions apps/studio/electron/main/code/diff/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,46 @@ function insertAttribute(element: t.JSXOpeningElement, attribute: string, classN
const newClassNameAttr = t.jsxAttribute(t.jsxIdentifier(attribute), t.stringLiteral(className));
element.attributes.push(newClassNameAttr);
}

export function updateNodeProp(node: t.JSXElement, key: string, value: any): void {
const openingElement = node.openingElement;
const existingAttr = openingElement.attributes.find(
(attr) => t.isJSXAttribute(attr) && attr.name.name === key,
) as t.JSXAttribute | undefined;

if (existingAttr) {
if (typeof value === 'boolean') {
existingAttr.value = t.jsxExpressionContainer(t.booleanLiteral(value));
} else if (typeof value === 'string') {
existingAttr.value = t.stringLiteral(value);
} else if (typeof value === 'function') {
existingAttr.value = t.jsxExpressionContainer(
t.arrowFunctionExpression([], t.blockStatement([])),
);
} else {
existingAttr.value = t.jsxExpressionContainer(t.identifier(value.toString()));
}
} else {
let newAttr: t.JSXAttribute;
if (typeof value === 'boolean') {
newAttr = t.jsxAttribute(
t.jsxIdentifier(key),
t.jsxExpressionContainer(t.booleanLiteral(value)),
);
} else if (typeof value === 'string') {
newAttr = t.jsxAttribute(t.jsxIdentifier(key), t.stringLiteral(value));
} else if (typeof value === 'function') {
newAttr = t.jsxAttribute(
t.jsxIdentifier(key),
t.jsxExpressionContainer(t.arrowFunctionExpression([], t.blockStatement([]))),
);
} else {
newAttr = t.jsxAttribute(
t.jsxIdentifier(key),
t.jsxExpressionContainer(t.identifier(value.toString())),
);
}

openingElement.attributes.push(newAttr);
}
}
37 changes: 20 additions & 17 deletions apps/studio/electron/main/code/diff/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { insertImageToNode, removeImageFromNode } from './image';
import { insertElementToNode } from './insert';
import { moveElementInNode } from './move';
import { removeElementFromNode } from './remove';
import { addClassToNode, replaceNodeClasses } from './style';
import { addClassToNode, replaceNodeClasses, updateNodeProp } from './style';
import { updateNodeTextContent } from './text';
import { assertNever } from '/common/helpers';

Expand All @@ -22,24 +22,27 @@ export function transformAst(ast: t.File, oidToCodeDiff: Map<string, CodeDiffReq
}
const codeDiffRequest = oidToCodeDiff.get(currentOid);
if (codeDiffRequest) {
if (
codeDiffRequest.attributes &&
codeDiffRequest.attributes.className !== null &&
codeDiffRequest.attributes.className !== undefined
) {
if (codeDiffRequest.overrideClasses) {
replaceNodeClasses(path.node, codeDiffRequest.attributes.className);
} else {
addClassToNode(path.node, codeDiffRequest.attributes.className);
}
const { attributes, textContent, structureChanges } = codeDiffRequest;

if (attributes) {
Object.entries(attributes).forEach(([key, value]) => {
if (key === 'className') {
if (codeDiffRequest.overrideClasses) {
replaceNodeClasses(path.node, value as string);
} else {
addClassToNode(path.node, value as string);
}
} else {
updateNodeProp(path.node, key, value);
}
});
}
if (
codeDiffRequest.textContent !== undefined &&
codeDiffRequest.textContent !== null
) {
updateNodeTextContent(path.node, codeDiffRequest.textContent);

if (textContent !== undefined && textContent !== null) {
updateNodeTextContent(path.node, textContent);
}
applyStructureChanges(path, codeDiffRequest.structureChanges);

applyStructureChanges(path, structureChanges);
}
},
});
Expand Down
87 changes: 87 additions & 0 deletions apps/studio/electron/main/code/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as t from '@babel/types';
import {
type NodeProps,
type PropsParsingResult,
PropsType,
type TemplateNode,
} from '@onlook/models/element';
import { readCodeBlock } from '.';
import { parseJsxCodeBlock } from './helpers';

export async function getTemplateNodeProps(
templateNode: TemplateNode,
): Promise<PropsParsingResult> {
const codeBlock = await readCodeBlock(templateNode);
if (codeBlock == null) {
console.error(`Failed to read code block: ${templateNode.path}`);
return { type: 'error', reason: 'Code block could not be read.' };
}
const ast = parseJsxCodeBlock(codeBlock);

if (!ast) {
return { type: 'error', reason: 'AST could not be parsed.' };
}

return getNodeAttributes(ast);
}

function getNodeAttributes(node: t.JSXElement): PropsParsingResult {
try {
const openingElement = node.openingElement;
const props: NodeProps[] = [];

openingElement.attributes.forEach((attr) => {
if (
!t.isJSXAttribute(attr) ||
attr.name.name === 'className' ||
attr.name.name === 'data-oid'
) {
return;
}

const attrName = attr.name.name;
let attrValue: boolean | string | number = true;
let attrType: PropsType = PropsType.Code;

if (attr.value) {
if (t.isStringLiteral(attr.value)) {
attrValue = attr.value.value;
attrType = PropsType.String;
} else if (t.isJSXExpressionContainer(attr.value)) {
const expr = attr.value.expression;
if (t.isBooleanLiteral(expr)) {
attrValue = expr.value;
attrType = PropsType.Boolean;
} else if (t.isStringLiteral(expr)) {
attrValue = expr.value;
attrType = PropsType.String;
} else if (t.isNumericLiteral(expr)) {
attrValue = expr.value;
attrType = PropsType.Number;
} else {
attrValue = `{${expr.type}}`;
attrType = PropsType.Code;
}
} else {
attrValue = `Unsupported type: ${attr.value.type}`;
attrType = PropsType.Code;
}
} else {
attrType = PropsType.Boolean;
}

props.push({ key: attrName, value: attrValue, type: attrType });
});

return {
type: 'props',
props,
};
} catch (error) {
console.error('Failed to parse component props:', error);
return {
type: 'error',
reason: 'Failed to parse component props.',
};
}
}
6 changes: 6 additions & 0 deletions apps/studio/electron/main/events/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { readFile } from '../code/files';
import { getTemplateNodeChild } from '../code/templateNode';
import runManager from '../run';
import { getFileContentWithoutIds } from '../run/cleanup';
import { getTemplateNodeProps } from '../code/props';

export function listenForCodeMessages() {
ipcMain.handle(MainChannels.VIEW_SOURCE_CODE, (e: Electron.IpcMainInvokeEvent, args) => {
Expand Down Expand Up @@ -112,4 +113,9 @@ export function listenForCodeMessages() {
return isChildTextEditable(oid);
},
);

ipcMain.handle(MainChannels.GET_TEMPLATE_NODE_PROPS, (e: Electron.IpcMainInvokeEvent, args) => {
const templateNode = args as TemplateNode;
return getTemplateNodeProps(templateNode);
});
}
42 changes: 32 additions & 10 deletions apps/studio/electron/main/events/hosting.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
import { MainChannels } from '@onlook/models/constants';
import type { PublishRequest, PublishResponse, UnpublishRequest } from '@onlook/models/hosting';
import { ipcMain } from 'electron';
import hostingManager from '../hosting';
import {
createDomainVerification,
getCustomDomains,
getOwnedDomains,
verifyDomain,
} from '../hosting/domains';

export function listenForHostingMessages() {
ipcMain.handle(MainChannels.START_DEPLOYMENT, async (e: Electron.IpcMainInvokeEvent, args) => {
const { folderPath, buildScript, urls, skipBuild } = args;
return await hostingManager.deploy(folderPath, buildScript, urls, skipBuild);
});

ipcMain.handle(
MainChannels.GET_CUSTOM_DOMAINS,
async (e: Electron.IpcMainInvokeEvent, args) => {
return await hostingManager.getCustomDomains();
MainChannels.PUBLISH_TO_DOMAIN,
async (_e: Electron.IpcMainInvokeEvent, args: PublishRequest): Promise<PublishResponse> => {
return await hostingManager.publish(args);
},
);

ipcMain.handle(
MainChannels.UNPUBLISH_HOSTING_ENV,
async (e: Electron.IpcMainInvokeEvent, args) => {
MainChannels.UNPUBLISH_DOMAIN,
async (
e: Electron.IpcMainInvokeEvent,
args: UnpublishRequest,
): Promise<PublishResponse> => {
const { urls } = args;
return await hostingManager.unpublish(urls);
},
);

ipcMain.handle(
MainChannels.CREATE_DOMAIN_VERIFICATION,
async (_e: Electron.IpcMainInvokeEvent, args) => {
const { domain } = args;
return await createDomainVerification(domain);
},
);

ipcMain.handle(MainChannels.VERIFY_DOMAIN, async (_e: Electron.IpcMainInvokeEvent, args) => {
const { domain } = args;
return await verifyDomain(domain);
});

ipcMain.handle(MainChannels.GET_OWNED_DOMAINS, async (_e: Electron.IpcMainInvokeEvent) => {
return await getOwnedDomains();
});
}
11 changes: 10 additions & 1 deletion apps/studio/electron/main/events/run.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { DetectedPortResults } from '@onlook/models';
import { MainChannels } from '@onlook/models/constants';
import { ipcMain } from 'electron';
import { runBunCommand } from '../bun';
import { isPortAvailable, runBunCommand } from '../bun';
import run from '../run';
import terminal from '../run/terminal';

Expand Down Expand Up @@ -61,4 +62,12 @@ export async function listenForRunMessages() {
const { cwd, command } = args as { cwd: string; command: string };
return await runBunCommand(command, { cwd });
});

ipcMain.handle(
MainChannels.IS_PORT_AVAILABLE,
async (e: Electron.IpcMainInvokeEvent, args): Promise<DetectedPortResults> => {
const { port } = args as { port: number };
return await isPortAvailable(port);
},
);
}
Loading

0 comments on commit 14dfc9d

Please sign in to comment.