Skip to content

Commit

Permalink
Write code in real-time (#407)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kitenite authored Oct 1, 2024
1 parent e20fb39 commit a1d58eb
Show file tree
Hide file tree
Showing 19 changed files with 746 additions and 595 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ Projects we're inspired by:
* [Responsively](https://github.com/responsively-org/responsively-app)
* [Supabase](https://github.com/supabase/supabase)
* [ShadCN](https://github.com/shadcn-ui/ui)
* [hymhub/css-to-tailwind](https://github.com/hymhub/css-to-tailwind)

## License

Expand Down
1 change: 0 additions & 1 deletion app/electron/main/code/diff/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ function processGroupedRequests(groupedRequests: Map<string, RequestsByPath>): C
const generated = generateCode(ast, generateOptions, codeBlock);
diffs.push({ original, generated, path });
}

return diffs;
}

Expand Down
2 changes: 1 addition & 1 deletion app/electron/main/code/diff/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,6 @@ function updateNodeTextContent(node: t.JSXElement, textContent: string): void {
if (textNode) {
textNode.value = textContent;
} else {
console.error('Text node not found');
node.children.unshift(t.jsxText(textContent));
}
}
4 changes: 2 additions & 2 deletions app/electron/preload/webview/changes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class CssStyleChange {
enter: (decl: Declaration) => {
if (decl.property === property) {
decl.value = { type: 'Raw', value: value };
if (value === '' || value === 'none') {
if (value === '') {
rule.block.children = rule.block.children.filter(
(decl: Declaration) => decl.property !== property,
);
Expand All @@ -115,7 +115,7 @@ export class CssStyleChange {
});

if (!found) {
if (value === '' || value === 'none') {
if (value === '') {
rule.block.children = rule.block.children.filter(
(decl: Declaration) => decl.property !== property,
);
Expand Down
2 changes: 1 addition & 1 deletion app/electron/preload/webview/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { LayerNode } from '/common/models/element/layers';
export function processDom(root: HTMLElement = document.body) {
const layerTree = buildLayerTree(root);
if (!layerTree) {
console.error('Error building layer tree');
console.error('Error building layer tree, root element is null');
return;
}
ipcRenderer.sendToHost(WebviewChannels.DOM_READY, layerTree);
Expand Down
14 changes: 10 additions & 4 deletions app/electron/preload/webview/elements/text.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { publishEditText } from '../events/publish';
import { getDomElement, restoreElementStyle, saveTimestamp } from './helpers';
import {
getDomElement,
getImmediateTextContent,
restoreElementStyle,
saveTimestamp,
} from './helpers';
import { EditorAttributes } from '/common/constants';
import { getUniqueSelector } from '/common/helpers';
import { TextDomElement } from '/common/models/element';
Expand Down Expand Up @@ -73,7 +78,7 @@ export function stopEditingText(): void {
if (!el) {
return;
}
cleanUpElementAfterDragging(el);
cleanUpElementAfterEditing(el);
publishEditText(getDomElement(el, true));
}

Expand Down Expand Up @@ -113,7 +118,7 @@ function prepareElementForEditing(el: HTMLElement) {
}
}

function cleanUpElementAfterDragging(el: HTMLElement) {
function cleanUpElementAfterEditing(el: HTMLElement) {
restoreElementStyle(el);
removeEditingAttributes(el);
saveTimestamp(el);
Expand All @@ -135,7 +140,8 @@ export function clearTextEditedElements() {

function updateTextContent(el: HTMLElement, content: string): void {
if (!el.hasAttribute(EditorAttributes.DATA_ONLOOK_ORIGINAL_CONTENT)) {
el.setAttribute(EditorAttributes.DATA_ONLOOK_ORIGINAL_CONTENT, el.textContent || '');
const originalContent = getImmediateTextContent(el);
el.setAttribute(EditorAttributes.DATA_ONLOOK_ORIGINAL_CONTENT, originalContent || '');
}
el.textContent = content;
}
3 changes: 1 addition & 2 deletions app/electron/preload/webview/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,9 @@ function listenForEditEvents() {
});

ipcRenderer.on(WebviewChannels.CLEAN_AFTER_WRITE_TO_CODE, () => {
change.clearStyleSheet();
removeInsertedElements();
clearMovedElements();
clearTextEditedElements();
setTimeout(processDom, 500);
processDom();
});
}
7 changes: 6 additions & 1 deletion app/electron/preload/webview/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import { processDom } from './dom';
import { listenForEvents } from './events';

function handleBodyReady() {
processDom();
keepDomUpdated();
setApi();
listenForEvents();
}

function keepDomUpdated() {
processDom();
setInterval(() => processDom(), 5000);
}

const handleDocumentBody = setInterval(() => {
window.onerror = function logError(errorMsg, url, lineNumber) {
console.log(`Unhandled error: ${errorMsg} ${url} ${lineNumber}`);
Expand Down
13 changes: 8 additions & 5 deletions app/src/lib/editor/engine/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import { TemplateNode } from '/common/models/element/templateNode';

export class AstManager {
private doc: Document | undefined;
displayLayers: LayerNode[] = [];
private displayLayers: LayerNode[] = [];
templateNodeMap: TemplateNodeMap = new TemplateNodeMap();

constructor() {
makeAutoObservable(this);
}

updateLayers(newLayers: LayerNode[]) {
this.displayLayers = newLayers;
get layers() {
return this.displayLayers;
}

set layers(layers: LayerNode[]) {
this.displayLayers = layers;
}

replaceElement(selector: string, newNode: LayerNode) {
Expand Down Expand Up @@ -78,7 +82,6 @@ export class AstManager {
}

setMapRoot(rootElement: Element) {
this.clear();
this.setDoc(rootElement.ownerDocument);

if (isOnlookInDoc(rootElement.ownerDocument)) {
Expand Down Expand Up @@ -165,6 +168,6 @@ export class AstManager {

clear() {
this.templateNodeMap = new TemplateNodeMap();
this.updateLayers([]);
this.displayLayers = [];
}
}
47 changes: 45 additions & 2 deletions app/src/lib/editor/engine/code/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
import { sendAnalytics } from '@/lib/utils';
import { CssToTailwindTranslator, ResultCode } from 'css-to-tailwind-translator';
import { WebviewTag } from 'electron';
import { debounce } from 'lodash';
import { makeAutoObservable, reaction } from 'mobx';
import { twMerge } from 'tailwind-merge';
import { AstManager } from '../ast';
import { HistoryManager } from '../history';
import { WebviewManager } from '../webview';
import { EditorAttributes, MainChannels } from '/common/constants';
import { EditorAttributes, MainChannels, WebviewChannels } from '/common/constants';
import { CodeDiff, CodeDiffRequest } from '/common/models/code';
import { InsertedElement, MovedElement, TextEditedElement } from '/common/models/element/domAction';
import { TemplateNode } from '/common/models/element/templateNode';

export class CodeManager {
isExecuting = false;
isQueued = false;

constructor(
private webviewManager: WebviewManager,
private astManager: AstManager,
) {}
private historyManager: HistoryManager,
) {
makeAutoObservable(this);
this.listenForUndoChange();
}

listenForUndoChange() {
reaction(
() => this.historyManager.length,
() => this.generateAndWriteCodeDiffs(),
);
}

viewSource(templateNode?: TemplateNode): void {
if (!templateNode) {
Expand All @@ -24,6 +41,32 @@ export class CodeManager {
sendAnalytics('view source code');
}

generateAndWriteCodeDiffs = debounce(this.undebouncedGenerateAndWriteCodeDiffs, 1000);

async undebouncedGenerateAndWriteCodeDiffs(): Promise<void> {
if (this.isExecuting) {
this.isQueued = true;
return;
}
const codeDiffs = await this.generateCodeDiffs();
if (codeDiffs.length === 0) {
console.error('No code diffs found.');
return;
}
const res = await window.api.invoke(MainChannels.WRITE_CODE_BLOCKS, codeDiffs);
if (res) {
this.webviewManager.getAll().forEach((webview) => {
webview.send(WebviewChannels.CLEAN_AFTER_WRITE_TO_CODE);
});
}

this.isExecuting = false;
if (this.isQueued) {
this.isQueued = false;
this.generateAndWriteCodeDiffs();
}
}

async generateCodeDiffs(): Promise<CodeDiff[]> {
const webviews = [...this.webviewManager.getAll().values()];
if (webviews.length === 0) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/lib/editor/engine/history/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export class HistoryManager {

switch (action.type) {
case 'update-style':
sendAnalytics('edit action', {
sendAnalytics('style action', {
type: action.type,
style: action.style,
new_value: action.change.updated,
Expand Down
7 changes: 5 additions & 2 deletions app/src/lib/editor/engine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export class EditorEngine {
private projectInfoManager: ProjectInfoManager = new ProjectInfoManager();
private canvasManager: CanvasManager;
private domManager: DomManager = new DomManager(this.astManager);
private codeManager: CodeManager = new CodeManager(this.webviewManager, this.astManager);
private elementManager: ElementManager = new ElementManager(
this.overlayManager,
this.astManager,
Expand All @@ -50,6 +49,11 @@ export class EditorEngine {
this.historyManager,
this.astManager,
);
private codeManager: CodeManager = new CodeManager(
this.webviewManager,
this.astManager,
this.historyManager,
);

constructor(private projectsManager: ProjectsManager) {
makeAutoObservable(this);
Expand Down Expand Up @@ -136,7 +140,6 @@ export class EditorEngine {
}

async refreshLayers() {
this.ast.clear();
const webviews = this.webviews.webviews;
if (webviews.size === 0) {
return;
Expand Down
3 changes: 1 addition & 2 deletions app/src/lib/editor/eventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@ export class WebviewEventHandler {
console.error('No args found for dom ready event');
return;
}
this.editorEngine.ast.clear();
const body = await this.editorEngine.dom.getBodyFromWebview(webview);
this.editorEngine.dom.setDom(webview.id, body);
const layerTree = e.args[0] as LayerNode;
this.editorEngine.ast.updateLayers([layerTree as LayerNode]);
this.editorEngine.ast.layers = [layerTree as LayerNode];
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const ColorInput = observer(
}, [elementStyle]);

function isNoneInput() {
return inputString === 'initial' || inputString === '';
return inputString === 'initial' || inputString === '' || inputString === 'transparent';
}

function sendStyleUpdate(newValue: string) {
Expand Down Expand Up @@ -71,7 +71,7 @@ const ColorInput = observer(
<button
className="text-text"
onClick={() => {
const newValue = isNoneInput() ? '#000000' : '';
const newValue = isNoneInput() ? '#000000' : 'transparent';
sendStyleUpdate(newValue);
}}
>
Expand Down
4 changes: 2 additions & 2 deletions app/src/routes/editor/LayersPanel/LayersTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import { LayerNode } from '/common/models/element/layers';
const LayersTab = observer(() => {
const treeRef = useRef<TreeApi<LayerNode>>();
const editorEngine = useEditorEngine();
const [domTree, setDomTree] = useState<LayerNode[]>(editorEngine.ast.displayLayers);
const [domTree, setDomTree] = useState<LayerNode[]>(editorEngine.ast.layers);
const [treeHovered, setTreeHovered] = useState(false);
const { ref, width, height } = useResizeObserver();

useEffect(() => setDomTree(editorEngine.ast.displayLayers), [editorEngine.ast.displayLayers]);
useEffect(() => setDomTree(editorEngine.ast.layers), [editorEngine.ast.layers]);
useEffect(handleSelectChange, [editorEngine.elements.selected]);

function handleMouseLeaveTree() {
Expand Down
2 changes: 0 additions & 2 deletions app/src/routes/editor/TopBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { observer } from 'mobx-react-lite';
import ModeToggle from './ModeToggle';
import OpenCode from './OpenCode';
import ProjectBreadcrumb from './ProjectSelect';
import PublishModal from './PublishModal';
import { Hotkey } from '/common/hotkeys';

const EditorTopBar = observer(() => {
Expand Down Expand Up @@ -63,7 +62,6 @@ const EditorTopBar = observer(() => {
<ModeToggle />
<div className="flex space-x-2 flex-grow basis-0 justify-end">
<OpenCode />
<PublishModal />
</div>
</div>
);
Expand Down
1 change: 0 additions & 1 deletion app/src/routes/editor/WebviewArea/Frame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ const Frame = observer(

function handleUrlChange(e: any) {
setWebviewSrc(e.url);
editorEngine.clear();
}

async function handleDomReady() {
Expand Down
Loading

0 comments on commit a1d58eb

Please sign in to comment.