Skip to content

Commit

Permalink
Toggle writing styles to root or instance (#776)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kitenite authored Nov 14, 2024
1 parent d81a3d6 commit c91dbf5
Show file tree
Hide file tree
Showing 17 changed files with 399 additions and 104 deletions.
13 changes: 8 additions & 5 deletions apps/studio/electron/main/code/diff/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ export function addClassToNode(node: t.JSXElement, className: string): void {
classNameAttr.value.expression.arguments.push(t.stringLiteral(className));
}
} else {
const newClassNameAttr = t.jsxAttribute(
t.jsxIdentifier('className'),
t.stringLiteral(className),
);
openingElement.attributes.push(newClassNameAttr);
insertAttribute(openingElement, 'className', className);
}
}

Expand All @@ -33,5 +29,12 @@ export function replaceNodeClasses(node: t.JSXElement, className: string): void

if (classNameAttr) {
classNameAttr.value = t.stringLiteral(className);
} else {
insertAttribute(openingElement, 'className', className);
}
}

function insertAttribute(element: t.JSXOpeningElement, attribute: string, className: string): void {
const newClassNameAttr = t.jsxAttribute(t.jsxIdentifier(attribute), t.stringLiteral(className));
element.attributes.push(newClassNameAttr);
}
8 changes: 4 additions & 4 deletions apps/studio/electron/main/code/diff/transform.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import traverse, { type NodePath } from '@babel/traverse';
import type * as t from '@babel/types';
import { type CodeAction, CodeActionType } from '@onlook/models/actions';
import type { CodeDiffRequest } from '@onlook/models/code';
import type { TemplateNode } from '@onlook/models/element';
import { getTemplateNode } from '../templateNode';
import { groupElementsInNode, ungroupElementsInNode } from './group';
import { addKeyToElement, createHashedTemplateToCodeDiff, hashTemplateNode } from './helpers';
Expand All @@ -9,9 +12,6 @@ import { removeElementFromNode } from './remove';
import { addClassToNode, replaceNodeClasses } from './style';
import { updateNodeTextContent } from './text';
import { assertNever } from '/common/helpers';
import { type CodeAction, CodeActionType } from '@onlook/models/actions';
import type { CodeDiffRequest } from '@onlook/models/code';
import type { TemplateNode } from '@onlook/models/element';

export function transformAst(
ast: t.File,
Expand All @@ -26,7 +26,7 @@ export function transformAst(
const codeDiffRequest = hashedTemplateToCodeDiff.get(hashedKey);

if (codeDiffRequest) {
if (codeDiffRequest.attributes && codeDiffRequest.attributes.className) {
if (codeDiffRequest.attributes && codeDiffRequest.attributes.className !== null) {
if (codeDiffRequest.overrideClasses) {
replaceNodeClasses(path.node, codeDiffRequest.attributes.className);
} else {
Expand Down
6 changes: 3 additions & 3 deletions apps/studio/electron/main/events/code.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import type { CodeDiff, CodeDiffRequest } from '@onlook/models/code';
import { MainChannels } from '@onlook/models/constants';
import type { TemplateNode } from '@onlook/models/element';
import { ipcMain } from 'electron';
import { openInIde, pickDirectory, readCodeBlock, readCodeBlocks, writeCode } from '../code/';
import { getTemplateNodeClass } from '../code/classes';
Expand All @@ -6,9 +9,6 @@ import { extractComponentsFromDirectory } from '../code/components';
import { getCodeDiffs } from '../code/diff';
import { readFile } from '../code/files';
import { getTemplateNodeChild } from '../code/templateNode';
import { MainChannels } from '@onlook/models/constants';
import type { CodeDiff, CodeDiffRequest } from '@onlook/models/code';
import type { TemplateNode } from '@onlook/models/element';

export function listenForCodeMessages() {
ipcMain.handle(MainChannels.VIEW_SOURCE_CODE, (e: Electron.IpcMainInvokeEvent, args) => {
Expand Down
6 changes: 5 additions & 1 deletion apps/studio/src/lib/editor/engine/code/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { MainChannels, WebviewChannels } from '@onlook/models/constants';
import type { TemplateNode } from '@onlook/models/element';
import { makeAutoObservable } from 'mobx';
import type { EditorEngine } from '..';
import { StyleMode } from '../style';
import { getGroupElement, getUngroupElement } from './group';
import { getOrCreateCodeDiffRequest, getTailwindClassChangeFromStyle } from './helpers';
import { getInsertedElement } from './insert';
Expand Down Expand Up @@ -270,7 +271,10 @@ export class CodeManager {
templateToCodeChange: Map<TemplateNode, CodeDiffRequest>,
): Promise<void> {
for (const change of styleChanges) {
const templateNode = this.editorEngine.ast.getAnyTemplateNode(change.selector);
const templateNode =
this.editorEngine.style.mode === StyleMode.Instance
? this.editorEngine.ast.getInstance(change.selector)
: this.editorEngine.ast.getRoot(change.selector);
if (!templateNode) {
continue;
}
Expand Down
48 changes: 31 additions & 17 deletions apps/studio/src/lib/editor/engine/style/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { makeAutoObservable, reaction } from 'mobx';
import type { EditorEngine } from '..';
import type { Change, StyleActionTarget, UpdateStyleAction } from '@onlook/models/actions';
import type { DomElement } from '@onlook/models/element';
import { makeAutoObservable, reaction } from 'mobx';
import type { EditorEngine } from '..';

export interface SelectedStyle {
styles: Record<string, string>;
parentRect: DOMRect;
rect: DOMRect;
}

export enum StyleMode {
Instance = 'instance',
Root = 'root',
}

export class StyleManager {
selectedStyle: SelectedStyle | null = null;
selectorToStyle: Map<string, SelectedStyle> = new Map();
private selectedElementsDisposer: () => void;
prevSelectedSignature: string = '';
mode: StyleMode = StyleMode.Root;

constructor(private editorEngine: EditorEngine) {
makeAutoObservable(this);
Expand All @@ -35,21 +42,19 @@ export class StyleManager {
selected = selected.filter((el) => selectors.includes(el.selector));
}

const targets: Array<StyleActionTarget> = this.editorEngine.elements.selected.map(
(selectedEl) => {
const change: Change<string> = {
updated: value,
original: selectedEl.styles[style],
};
const target: StyleActionTarget = {
webviewId: selectedEl.webviewId,
selector: selectedEl.selector,
change: change,
uuid: selectedEl.uuid,
};
return target;
},
);
const targets: Array<StyleActionTarget> = selected.map((selectedEl) => {
const change: Change<string> = {
updated: value,
original: selectedEl.styles[style],
};
const target: StyleActionTarget = {
webviewId: selectedEl.webviewId,
selector: selectedEl.selector,
change: change,
uuid: selectedEl.uuid,
};
return target;
});
return {
type: 'update-style',
targets: targets,
Expand All @@ -75,6 +80,15 @@ export class StyleManager {
}

private onSelectedElementsChanged(selectedElements: DomElement[]) {
const newSelected = selectedElements
.map((el) => el.selector)
.toSorted()
.join();
if (newSelected !== this.prevSelectedSignature) {
this.mode = StyleMode.Root;
}
this.prevSelectedSignature = newSelected;

if (selectedElements.length === 0) {
this.selectorToStyle = new Map();
return;
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/lib/editor/styles/autolayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export function parseModeAndValue(value: string): {
mode: LayoutMode;
layoutValue: string;
} {
if (value === 'fit-content' || value === 'auto') {
return { mode: LayoutMode.Fit, layoutValue: value };
if (value === 'fit-content' || value === 'auto' || value === '') {
return { mode: LayoutMode.Fit, layoutValue: '' };
}
if (value === '100%' || value === 'auto') {
return { mode: LayoutMode.Fill, layoutValue: '100%' };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEditorEngine } from '@/components/Context';
import { Button } from '@onlook/ui/button';
import { Icons } from '@onlook/ui/icons';
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '@onlook/ui/popover';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@onlook/ui/tooltip';
import { Tooltip, TooltipContent, TooltipTrigger } from '@onlook/ui/tooltip';
import { cn } from '@onlook/ui/utils';
import { TooltipArrow } from '@radix-ui/react-tooltip';
import { observer } from 'mobx-react-lite';
Expand All @@ -16,27 +16,25 @@ const ChatHistory = observer(() => {

return (
<Popover open={isHistoryOpen} onOpenChange={setIsHistoryOpen}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<PopoverTrigger asChild disabled={editorEngine.chat.isWaiting}>
<Button
variant={'ghost'}
size={'icon'}
className={cn('p-2 w-fit h-fit', {
'bg-background-secondary text-primary': isHistoryOpen,
})}
>
<Icons.CounterClockwiseClock />
</Button>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>Chat History</p>
<TooltipArrow className="fill-foreground" />
</TooltipContent>
</Tooltip>
</TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<PopoverTrigger asChild disabled={editorEngine.chat.isWaiting}>
<Button
variant={'ghost'}
size={'icon'}
className={cn('p-2 w-fit h-fit', {
'bg-background-secondary text-primary': isHistoryOpen,
})}
>
<Icons.CounterClockwiseClock />
</Button>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>Chat History</p>
<TooltipArrow className="fill-foreground" />
</TooltipContent>
</Tooltip>
<PopoverAnchor className="absolute -left-2 top-0" />
<PopoverContent side="left" align="start" className="rounded-xl p-0">
<div className="flex flex-col">
Expand Down
40 changes: 36 additions & 4 deletions apps/studio/src/routes/editor/EditPanel/StylesTab/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEditorEngine } from '@/components/Context';
import { StyleMode } from '@/lib/editor/engine/style';
import type { CompoundStyleImpl } from '@/lib/editor/styles';
import { LayoutGroup, PositionGroup, StyleGroup, TextGroup } from '@/lib/editor/styles/group';
import {
Expand All @@ -9,6 +10,9 @@ import {
StyleType,
} from '@/lib/editor/styles/models';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@onlook/ui/accordion';
import { Icons } from '@onlook/ui/icons/index';
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from '@onlook/ui/tooltip';
import { cn } from '@onlook/ui/utils';
import { observer } from 'mobx-react-lite';
import BorderInput from './compound/BorderInput';
import DisplayInput from './compound/DisplayInput';
Expand Down Expand Up @@ -95,11 +99,39 @@ const ManualTab = observer(() => {
});
}

function renderAccordianHeader(groupKey: string) {
return (
<Tooltip>
<TooltipTrigger asChild disabled={editorEngine.style.mode !== StyleMode.Instance}>
<div
className={cn(
'text-xs flex transition-all items-center group',
editorEngine.style.mode === StyleMode.Instance &&
'gap-1 text-purple-600 dark:text-purple-300 hover:text-purple-500 dark:hover:text-purple-200',
)}
>
<Icons.ComponentInstance
className={cn(
'transition-all w-0',
editorEngine.style.mode === StyleMode.Instance &&
'w-3 h-3 text-purple-600 dark:text-purple-300 group-hover:text-purple-500 dark:group-hover:text-purple-200',
)}
/>
{groupKey}
</div>
</TooltipTrigger>
<TooltipPortal container={document.getElementById('style-tab-id')}>
<TooltipContent>{'Changes apply to instance code.'}</TooltipContent>
</TooltipPortal>
</Tooltip>
);
}

function renderStyleSections() {
return Object.entries(STYLE_GROUP_MAPPING).map(([groupKey, baseElementStyles]) => (
<AccordionItem key={groupKey} value={groupKey}>
<AccordionTrigger>
<h2 className="text-xs font-semibold">{groupKey}</h2>
<AccordionTrigger className="mx-0">
{renderAccordianHeader(groupKey)}
</AccordionTrigger>
<AccordionContent>
{groupKey === StyleGroupKey.Text && <TagDetails />}
Expand All @@ -113,7 +145,7 @@ const ManualTab = observer(() => {
return (
<AccordionItem key={TAILWIND_KEY} value={TAILWIND_KEY}>
<AccordionTrigger>
<h2 className="text-xs font-semibold">Tailwind Classes</h2>
<h2 className="text-xs">Tailwind Classes</h2>
</AccordionTrigger>
<AccordionContent>
<TailwindInput />
Expand All @@ -125,7 +157,7 @@ const ManualTab = observer(() => {
return (
editorEngine.elements.selected.length > 0 && (
<Accordion
className="px-4"
className="px-3"
type="multiple"
defaultValue={[...Object.values(StyleGroupKey), TAILWIND_KEY]}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const VALUE_OVERRIDE: Record<string, string | undefined> = {
const AutoLayoutInput = observer(({ elementStyle }: { elementStyle: SingleStyle }) => {
const editorEngine = useEditorEngine();
const [value, setValue] = useState(elementStyle.defaultValue);
const [prevValue, setPrevValue] = useState(elementStyle.defaultValue);

useEffect(() => {
const selectedStyle = editorEngine.style.selectedStyle;
Expand All @@ -40,6 +41,12 @@ const AutoLayoutInput = observer(({ elementStyle }: { elementStyle: SingleStyle
}, [editorEngine.style.selectedStyle]);

const emitValue = (newValue: string) => {
if (newValue === 'auto' || newValue === 'fit-content' || newValue === '') {
setValue(newValue);
sendStyleUpdate(newValue);
return;
}

const { layoutValue, mode } = parseModeAndValue(newValue);
const numValue = parseFloat(layoutValue);

Expand Down Expand Up @@ -106,7 +113,9 @@ const AutoLayoutInput = observer(({ elementStyle }: { elementStyle: SingleStyle
};

const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
emitValue(e.currentTarget.value);
if (e.currentTarget.value !== prevValue) {
emitValue(e.currentTarget.value);
}
editorEngine.history.commitTransaction();
};

Expand All @@ -122,7 +131,10 @@ const AutoLayoutInput = observer(({ elementStyle }: { elementStyle: SingleStyle
onKeyDown={(e) =>
handleNumberInputKeyDown(e, elementStyle, value, setValue, sendStyleUpdate)
}
onFocus={editorEngine.history.startTransaction}
onFocus={() => {
setPrevValue(overrideValue());
editorEngine.history.startTransaction();
}}
onBlur={handleBlur}
/>
<div className="relative w-16">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const ColorInput = observer(
// Input
const [isFocused, setIsFocused] = useState(false);
const [stagingInputValue, setStagingInputValue] = useState(value);
const [prevInputValue, setPrevInputValue] = useState(value);
const inputValue = isFocused ? stagingInputValue : value;

useEffect(() => {
Expand Down Expand Up @@ -50,13 +51,16 @@ const ColorInput = observer(

const handleFocus = () => {
setStagingInputValue(value);
setPrevInputValue(value);
setIsFocused(true);
editorEngine.history.startTransaction();
};

const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
const formattedColor = Color.from(e.currentTarget.value);
sendStyleUpdate(formattedColor);
if (prevInputValue !== e.currentTarget.value) {
const formattedColor = Color.from(e.currentTarget.value);
sendStyleUpdate(formattedColor);
}

setIsFocused(false);
editorEngine.history.commitTransaction();
Expand Down
Loading

0 comments on commit c91dbf5

Please sign in to comment.